Why is hardware acceleration not working on my View? - android

I'm using Facebook's Rebound library to replicate the bouncy animations seen in their chat heads implementation. The problem is, most of the time the animation stutters. A few pictures will explain this better. Here's the buttery-smooth chat heads animation:
And here's my attempt (notice how the animation for the white View skips nearly all frames):
Once in a while it works smoothly:
Below is the code I'm using currently (the entire project is up on Github if you want to set it up quickly). I'm guessing this has something to do with hardware acceleration not being enabled correctly on my View. There are 2 Springs in my SpringSystem, one for the "bubble" (the Android icon) and another for the content (the white View that is displayed on tapping the bubble). Any help on how to solve this issue would be greatly appreciated. Thanks.
AndroidManifest.xml:
<application android:hardwareAccelerated="true" ...>
...
</application>
AppService.java:
// the following code is in AppService#onCreate()
// AppService extends android.app.Service
// full code at https://github.com/vickychijwani/BubbleNote
mContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
final Spring bubbleSpring = system.createSpring();
bubbleSpring.setCurrentValue(1.0);
bubbleSpring.addListener(new SpringListener() {
#Override
public void onSpringUpdate(Spring spring) {
float value = (float) spring.getCurrentValue();
params.x = (int) (mPos[0] * value);
params.y = (int) (mPos[1] * value);
mWindowManager.updateViewLayout(mBubble, params);
// fire the second animation when this one is about to end
if (spring.isOvershooting() && contentSpring.isAtRest()) {
contentSpring.setEndValue(1.0);
}
}
// ...
});
final Spring contentSpring = system.createSpring();
contentSpring.setCurrentValue(0.0);
contentSpring.addListener(new SpringListener() {
#Override
public void onSpringUpdate(Spring spring) {
// always prints false?!
Log.d(TAG, "hardware acc = " + mContent.isHardwareAccelerated());
float value = (float) spring.getCurrentValue();
// clamping is required to prevent flicker
float clampedValue = Math.min(Math.max(value, 0.0f), 1.0f);
mContent.setScaleX(value);
mContent.setScaleY(value);
mContent.setAlpha(clampedValue);
}
// ...
});

I've figured it out by going through the framework source code.
TL;DR: add WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED to the layout flags when you manually attach a View to a Window / WindowManager; setting android:hardwareAccelerated=true in the manifest won't work.
I'm manually attaching my View to the WindowManager (because I need to create my UI in a Service to emulate chat heads) like so:
// code at https://github.com/vickychijwani/BubbleNote/blob/eb708e3910a7279c5490f614a7150009b59bad0b/app/src/main/java/io/github/vickychijwani/bubblenote/BubbleNoteService.java#L54
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false);
// ...
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
// ...
mWindowManager.addView(mBubble, params);
Let's go digging...
Welcome to the Android framework
I started debugging at View#draw(...), then went up the call stack to ViewRootImpl#draw(boolean). Here I came across this piece of code:
if (!dirty.isEmpty() || mIsAnimating) {
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
mHardwareYOffset = yoff;
mResizeAlpha = resizeAlpha;
mCurrentDirty.set(dirty);
dirty.setEmpty();
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (attachInfo.mHardwareRenderer != null &&
!attachInfo.mHardwareRenderer.isEnabled() &&
attachInfo.mHardwareRenderer.isRequested()) {
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
mHolder.getSurface());
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
In my case ViewRootImpl#drawSoftware() was being called, which uses the software renderer. Hmm... that means the HardwareRenderer is null. So I went searching for the point of construction of the HardwareRenderer, which is in ViewRootImpl#enableHardwareAcceleration(WindowManager.LayoutParams):
// Try to enable hardware acceleration if requested
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
// ...
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
// ...
}
Aha! There's our culprit!
Back to the problem at hand
In this case Android does not automatically set FLAG_HARDWARE_ACCELERATED for this Window, even though I've set android:hardwareAccerelated=true in the manifest. So the fix is simply:
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mBubble = (LinearLayout) inflater.inflate(R.layout.bubble, null, false);
// ...
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
// NOTE
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
// ...
mWindowManager.addView(mBubble, params);
Although the animation is still not as smooth as Facebook's. I wonder why... (before anyone asks: no, there are no copious logs during the animation; and yes, I've tried with a release build)

Related

Sometimes this method for detecting user interaction doesn't work

my Android application has to detect the user interactions in order to use method A or method B in a loop.
I call this method in the onCreate() of the MainActivity...
#SuppressLint("ClickableViewAccessibility")
private void detectUserInteraction() {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
0, 0, 0, 0,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
if (wm != null) {
final View view = new View(this);
view.setOnTouchListener((v, event) -> {
UserInteractionManager.getInstance().setRecentUserInteraction(true);
return false;
});
wm.addView(view, params);
}
}
...and is not working for a few part of the users (latest Huawei owners). I have the log files of these users and the method only works as I expected a few minutes.
Anyone has any idea to fix the issue of the method?
Thank you a lot.

Detect press of back button

I want to detect pressing of a back button in service. I've just tried this code but it didn't show me any log. Can somebody explain me why? And what should I do to make it work?
The whole idea of doing this was was taken from this tutorial http://www.kpbird.com/2013/03/android-detect-global-touch-event.html
public class MyService extends Service implements View.OnKeyListener{
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
LinearLayout touchLayout = new LinearLayout(this);
// set layout width 30 px and height is equal to full screen
LayoutParams lp = new LayoutParams(30, LayoutParams.MATCH_PARENT);
touchLayout.setLayoutParams(lp);
touchLayout.setBackgroundColor(Color.RED);
touchLayout.setOnKeyListener(this);
WindowManager mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// set layout parameter of window manager
WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
30, // width of layout 30 px
WindowManager.LayoutParams.MATCH_PARENT, // height is equal to full screen
WindowManager.LayoutParams.TYPE_PHONE, // Type Phone, These are non-application windows providing user interaction with the phone (in particular incoming calls).
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // this window won't ever get key input focus
PixelFormat.TRANSLUCENT);
mParams.gravity = Gravity.LEFT | Gravity.TOP;
mWindowManager.addView(touchLayout, mParams);
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK){
Log.v("Point","KeyCode_Back");
return false;
}
return false;
}
}
Your Service is not a View, implementing a View.OnKeyListener does not deliver your desired functionality.
A Service is intended to be an "Activity without UI" which runs in the background of your application. You can use Binders/Broadcasts to communicate with your service but UI interaction is best left to Activity/Fragments.
Annex:
I guess you are trying to build a overlay like in the link you posted in the comment. This Tutorial is from 2013 so things have changed.
In general the Android system discourages App beheaviour like the below described method. Coding like this, goes into the category Lockscreen/Kiosk-App behaviour which is considered as malware.
If you want to accomplish a little side menu inside your app you can do this perfectly fine without using such a service. Outside your App you still have the options of using widgets, which are more user friendly than hardcoding something on the screen.

turn off the display even if the app is in background

I use the following code to create on the flight a window used as preview when a picture is taken:
void CreatePreviewDialog()
{
dummy_frame_layout = new DummyFrameLayout(context);
wm_params = new WindowManager.LayoutParams(
240, 320, 0, 0,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
wm_params.gravity = Gravity.CENTER;
wm_params.setTitle("Preview");
window_manager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
window_manager.addView(dummy_frame_layout, wm_params);
}
My app runs in background and it is waked-up by an alarm. It turns-on the display as you can see. As soon as the picture is taken, the window is destroyed using the following method:
void DestroyPreviewDialog()
{
((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).removeView(dummy_frame_layout);
dummy_frame_layout = null;
}
The problem is that the screen remains on. I would like to turn-off the screen when the preview window is closed. How does DestroyPreviewDialog() should be modified in such a way to turn-off the display? (of course the display should be turned off only if it was found turned off when CreatePreviewDialog() was called, but this is simple. For now I need a way to turn off the display)
EDIT
I've modified DestroyPreviewWindow() as follows:
private void DestroyPreviewDialog()
{
wm_params.screenBrightness = 0.0f;
window_manager.updateViewLayout(dummy_frame_layout, wm_params);
((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).removeView(dummy_frame_layout);
dummy_frame_layout = null;
}
but the results does not change. Screen remains on and bright!

I want to create a button located on the top of all activities dynamically

I created it in DialpadFragment, but when the MO call was making, the InCallScreen would always overlay my button. Here is my code:
private void createStopButton() {
if (mstopButton == null) {
mstopButton = new Button(mActivity);
mstopButton.setText("Stop Dial");
}
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY|
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.OPAQUE);
params.gravity = Gravity.CENTER | Gravity.TOP;
final WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
mstopButton.setOnClickListener( new View.OnClickListener() {
public void onClick(View view) {
mIsRedialStatus = false;
wm.removeView(mstopButton);
}
});
wm.addView(mstopButton, params);
}
Anybody can help fix how to bring my button up the top of the screen. I have investigated lots of code in internet, I found that they put the top layer related code in a service.
Should I also implement it with a service? Thanks very much!
I just answered one similar to this. Check out the the first part of this answer: Creating bodyless activity
But yes, you'll need to do this via a service. Just remember you'll need to find a way to start the service (easiest way is to use an activity).

Android overlay layout on top of all windows that receives touches

I have created a view that displays on top of all applications and windows with the following code:
//These three are our main components.
WindowManager wm;
LinearLayout ll;
WindowManager.LayoutParams ll_lp;
//Just a sample layout parameters.
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
ll_lp = new WindowManager.LayoutParams();
ll_lp.format = PixelFormat.TRANSLUCENT;
ll_lp.height = WindowManager.LayoutParams.FILL_PARENT;
ll_lp.width = WindowManager.LayoutParams.FILL_PARENT;
ll_lp.gravity = Gravity.CLIP_HORIZONTAL | Gravity.TOP;
//This one is necessary.
ll_lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
//Play around with these two.
ll_lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
ll_lp.flags = ll_lp.flags | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//This is our main layout.
ll = new LinearLayout(this);
ll.setBackgroundColor(android.graphics.Color.argb(50, 255, 255, 255));
ll.setHapticFeedbackEnabled(true);
ll.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(getApplicationContext(), "TOUCHED", Toast.LENGTH_SHORT).show();
return false;
}
});
//And finally we add what we created to the screen.
wm.addView(ll, ll_lp);
Because FLAG_NOT_TOUCHABLE is set, it only displays the view but doesn't receive any touch events. The application behind the view receives all touch events.
However, if i don't set the flag, then only the view receives touches causing the application behind it to not receive any.
Is there a way for both the view and the application behind it to receive touches? I have tried returning false but still the same.
Any help would be greatly appreciated!
If im not mistaking, the view thats in the background is not receiving touch events because its being filtered out by the system to prevent "click jacking" exploits.
You might be able to get around this system feature by turning off the filtering of touch events for the view in the background using the View.setFilterTouchesWhenObscured(Boolean) method.

Categories

Resources