I am making an app that creates tiny sprite animations that walk around your screen.
I have a main activity, with a button "start service". This starts a service, which (in onCreate()
) creates a full-screen view and attaches it to the root window manager.
This part works perfectly. It fills the screen, and you can leave the app, and the animations will still be visible over everything.
The problem emerges when you rotate the device.
Sprites have moved to the middle of the screen, but this is an unrelated issue; the important thing here is the dark bounding box — this box shows you the canvas I am allowed to draw into, and it needs to fill the whole screen
The view is still portrait-mode, even though all other layouts seem to have updated correctly.
Part of the problem comes from how I have specified the dimensions of the view. I used flags to specify "full screen", but this did not set the view's width
and height
to match the screen's dimensions. Thus I had to set those manually at startup.
I don't know a way to update the width and height of the view once the view is created, but I feel that I need to, since the dimensions of the view determine the dimensions of the canvas.
My service creates the view like so:
public class WalkerService extends Service {
private WalkerView view;
@Override
public void onCreate() {
super.onCreate();
final WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
view = new WalkerView(this); // extends SurfaceView
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, // TYPE_SYSTEM_ALERT is denied in apiLevel >=19
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
);
view.setFitsSystemWindows(false); // allow us to draw over status bar, navigation bar
// here I _manually_ set the dimensions, because otherwise it defaults to a square (something like 1024x1024)
params.width = metrics.widthPixels;
params.height = metrics.heightPixels;
wm.addView(view, params);
}
}
I have tried listening for orientation changes, and rotating the view when that happens:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
view.setRotation(
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
? 90.0f
: 0.0f
);
}
But setRotation()
didn't seem to affect width
and height
of my view, nor did they change the angle at which the canvas was rendered.
Am I fundamentally misunderstanding how to create a full-screen overlay? How can I maintain the ability to draw over the entire screen, regardless of orientation (and even when my app is not the active app)?
Maybe it's related to the fact that I attach my view to the Window Service's window manager — perhaps this means my view has an odd parent (I could imagine that maybe the root view would not be affected by orientation, or would have no defined boundaries for children to stretch to fill).
Full source code is public on GitHub in case I've omitted any useful information here.
WalkerView.java
may be particularly relevant, so here it is.
I was misled by the answer to a previous question "How to create always-top fullscreen overlay activity in Android". Maybe it worked for an older API level? I'm using API level 24.
That particular answer had recommended:
There is a problem with this. The constructors that exist for
WindowManager.LayoutParams
are as follows:So the
WindowManager.LayoutParams.FLAG_FULLSCREEN
flag gets used as an explicit value forint w
andint h
. This is no good!I found that the correct construction for a full-screen overlay is like so:
This means we no longer explicitly specify a width and height. The layout relies entirely on our flags instead.
Yes,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
is a required flag still; it is necessary if you want to draw over decorations such as the status bar.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
should be used instead ofTYPE_SYSTEM_ALERT
in API level >=19.Bonus notes (if you're reading this, you're probably trying to make a full-screen overlay):
Your manifest will need the following permissions (explanation here):
My understanding is that
SYSTEM_ALERT_WINDOW
is the actual permission required, but thatACTION_MANAGE_OVERLAY_PERMISSION
is needed also: it lets you request at runtime that the user grant theSYSTEM_ALERT_WINDOW
privilege.