I have an activity that creates a view of type TYPE_SYSTEM_ALERT and I set a touch listener to the view. Is it possible to record the touch but still send the touch event to other views (example: to a widget from my home screen)?
Here is my code:
public class TestGestures extends Activity implements View.OnTouchListener {
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final WindowManager.LayoutParams param=new WindowManager.LayoutParams();
param.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final View view=findViewById(R.id.view1);
final ViewGroup parent=(ViewGroup)view.getParent();
if(parent!=null)
parent.removeView(view);
param.format= PixelFormat.RGBA_8888;
param.type=WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
param.gravity= Gravity.TOP|Gravity.LEFT;
param.width=view.getLayoutParams().width;
param.height=view.getLayoutParams().height;
//view.setBackgroundColor(android.R.color.white);
final WindowManager wmgr=(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wmgr.addView(view,param);
view.setOnTouchListener(this);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return false;
}
}
When I'm in the home screen, the touch events are not sent to the widgets
You'll have to play around with the parameters that you're using to setup your WindowManager.LayoutParams. For example, the code below will make the touch events be delivered to the Activity (or View) which is below the view that you just created:
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
On the other hand, if you change TYPE_SYSTEM_OVERLAY to TYPE_SYSTEM_PHONE, the input events will be delivered to the View that is on top of the other ones:
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
So, in your case, since you want the input events to be delivered to the views below, try setting the TYPE_SYSTEM_OVERLAY flag.
Related
I'm new to the accessibility stuff on Android. While going through the classes and documentation I came across TYPE_ACCESSIBILITY_OVERLAY inside the WindowManager class.
The documentation says (only the relevant text)
For example, if there is a full screen accessibility overlay that is
touchable, the windows below it will be introspectable by an
accessibility service even though they are covered by a touchable
window.
So I set out to achieve just that, a full screen accessibility overlay and try to introspect the windows below it
Extended AccessibilityService and added my full screen overlay when onServiceConnected is called (the inspiration for adding overlay came from here)
#Override
protected void onServiceConnected() {
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
FrameLayout mLayout = new FrameLayout(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
lp.format = PixelFormat.TRANSLUCENT;
lp.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
lp.gravity = Gravity.TOP;
wm.addView(mLayout, lp);
mLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// Here I'm getting the touch events on the overlay I added
return false;
}
});
}
Now, the question is, how do I introspect or find the windows below this overlay? Even in the onAccessibilityEvent callback I get just this overlay window. getWindows() always has a size of 1. Doesn't it refute the assertion made above for TYPE_ACCESSIBILITY_OVERLAY?
Relevant info: To receive the touch events on the overlay I have disabled touchExplorationMode in the service settings
android:canRequestTouchExplorationMode="false"
What you seem to be missing is flagRetrieveInteractiveWindows on your configuration. These properties and window layout paremeters configuration should work, without requiring for you to disable canRequestTouchExplorationMode in order to get the events and having getWindows return the AccessibilityWindowInfo instances underneath yours:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:packageNames="test.demo.com.tests"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagRetrieveInteractiveWindows|flagReportViewIds|flagIncludeNotImportantViews"
android:accessibilityFeedbackType="feedbackAllMask"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
/>
And on service connected:
#Override
protected void onServiceConnected() {
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
FrameLayout layout = new FrameLayout(this);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS|
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP;
windowManager.addView(layout, params);
layout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
//You can either get the information here or on onAccessibilityEvent
return false;
}
});
}
EDIT:
Added FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS to accomplish full screen and removed canRequestTouchExplorationMode since the flag associated to this property should not be included and, therefore, of no use.
I'm showing a overlay window from service in my app. I want to hide the window when press back button. So to get the back button event I do the following code
class MyView extends MyLayout{
public MyView(Context context){
super(context);
LayoutInflater.from(context).inflater(R.layout.my_view,this);
}
}
Class MyLayout extends FrameLayout{
public MyLayout(Context context){
super(context);
}
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.e("key event", "" + event.getKeyCode());
return super.dispatchKeyEvent(event);
}
}
//service code
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS |
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
PixelFormat.TRANSLUCENT);
final MyView myview = new MyView(this);
windowManager.addView(myview,param);
But dispatchKeyEvent method never called on back button pressed. I Googled and found out this is the way to capture the back button event. Somehow it's not working in my case. What am I missing ?
If you just want to capture the back button pressed event use:
#override
public void onBackPressed(){
// Do what you need done here
// ... or pass super.onBackPressed();
}
Real problem is the window params. I need to only WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH flags which prevent other app to get key events. So the correct code is following
//service code
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS |
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
PixelFormat.TRANSLUCENT);
I am adding a textview to Window using the following code
public static void addViewToWindow(Activity activity, View view, WindowManager.LayoutParams params)
{
WindowManager windowManager = (WindowManager)activity.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(view, params);
}
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.OPAQUE);
params.gravity = Gravity.BOTTOM;
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
addViewToWindow(myActivity, myEditText, params);
But I am not able to long press on the EditText to let the user copy/paste. As soon as I change the orientation of the device to landscape, the EditText covers the entire screen and I am able to copy/paste by long press. I am not able to make out what I am doing wrong.
I have a dynamically created Overlay Window that has an OnTouchListener on a certain layout in the Window. When I detect a a touch with the OnTouchListener, I wish to remove this Overlay Window. This overlay is dynamically created and not part of an activity so does not follow the the Activity lifecycle, hence I can't call finish(). How do I remove the view then? The code snippet below is where I create the View, and where my OnTouchListener is. How should I remove the overlay?
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
flags,
PixelFormat.TRANSLUCENT);
LinearLayout l = new LinearLayout(service);
RelativeLayout mainView = new RelativeLayout(service);
RelativeLayout.LayoutParams param_one = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
param_one.leftMargin=0;
param_one.topMargin=0;
l.setTag("first_tag");
l.setOrientation(LinearLayout.VERTICAL);
l.setBackgroundColor(0x00ffffff);
EditText second_test = new EditText(service);
second_test.setBackgroundColor(Color.parseColor("#0b0b0b")); //TODO: not perfect color
second_test.setTextColor(Color.LTGRAY);
second_test.setWidth(440);
second_test.setTag("second_tag");
second_test.setTextSize(20);
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
second_test.setLayoutParams(llp);
l.addView(second_test);
mainView.addView(l, param_one);
LinearLayout bottomLayout = new LinearLayout(service);
RelativeLayout.LayoutParams bottomparam = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
bottomparam.width=700;
bottomparam.height=120;
bottomparam.leftMargin=10;
bottomparam.topMargin=260;
bottomLayout.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch");
// Remove the overlay here
return true;
}
});
mainView.addView(bottomLayout, bottomparam);
I tried to call WindowManager.removeView(thisView) but system services such as WindowManager are not available to Activities before onCreate().
I have a window manager set up and it has the flag FLAG_NOT_FOCUSABLE which means it will not get the focus of key events. Is there anyway to reverse this upon a click for example? For example, if I click on a button, I want the window to be clickable. How could I do this?
Simply update the window manager with the new parameters.
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//remove FLAG_NOT_FOCUSABLE
params.flags = params.flags & ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//update the view
windowManager.updateViewLayout(view, params);
}
});