Update : Problem is solved.
WARNING : This question Does not have the answer AT ALL which is mentioned above.
The problem is, there should be WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY instead of TYPE_PHONE.
If you are having the same problem I would suggest to ask for permission in java class as shown in the link mentioned by 0X0nosugar in the comments.
I am running a service which creates a transparent floating window with some information and user can drag and drop it anywhere.
So far, after adding all views, my service Crashed and I got this error.
java.lang.RuntimeException:
Unable to create service afm.dragger.Dragger:
android.view.WindowManager$BadTokenException:
Unable to add window android.view.ViewRootImpl$W#ba63d06 -- permission denied for window type 2002
My minSdkVersion is 21 and targetSdkVersion is 27 and I have sdk 27 on my phone.
Here is my AndroidManifest:
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".Dragger"/>
</application>
Here is my Dragger.class:
#Override
public void onCreate() {
super.onCreate();
WindowManager manager = (WindowManager) getSystemService(WINDOW_SERVICE);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setBackgroundColor(Color.parseColor("#33FFFFFF"));
LinearLayout.LayoutParams linearParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
linearLayout.setLayoutParams(linearParams);
Button button = new Button(this);
button.setBackground(getResources().getDrawable(R.drawable.button_shape));
button.setTextColor(getResources().getColor(android.R.color.white));
button.setTextSize(14);
button.setText("Stop Service");
ViewGroup.LayoutParams buttonParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
button.setLayoutParams(buttonParams);
linearLayout.addView(button, buttonParams);
final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(350, 250, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
windowParams.x = 0;
windowParams.y = 0;
windowParams.gravity = Gravity.START|Gravity.TOP;
manager.addView(linearLayout, windowParams);
linearLayout.setOnTouchListener(new View.OnTouchListener() {
private WindowManager.LayoutParams updatedParams = windowParams;
int x, y;
float touchedX, touchedY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = updatedParams.x;
y = updatedParams.y;
touchedX = event.getRawX();
touchedY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
updatedParams.x = (int) (x + (event.getRawX() - touchedX));
updatedParams.y = (int) (y + (event.getRawY() - touchedY));
manager.updateViewLayout(linearLayout, updatedParams);
default:
break;
}
return false;
}
});
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
manager.removeView(linearLayout);
stopSelf();
}
});
}
And finally, here is my MainActivity:
start.setOnClickListener is set to :
startService(new Intent(MainActivity.this, Dragger.class));
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 need a view to show up at the top of my application, and when it's shown, it can keep showing at the top of all my application's other view(all the fragment and activity). It sounds like a Floating Action Button, but will always show at the top of my app.
I know I can do it via adding view to my phone's WindowManager, and hide it when I quit my app, show it again when I resume my app. This tricky method can work, but it also require some additional permission, which is I am trying to avoid.
If I only want to show in my app, can I achieve it without asking additional permission from user? If the answer is yes, then how? The key seems like some LayoutParams for the view, I tried but failed.
Would be nice if answer can show some detail code and explanation.
You have to use WindowManager for this purpose
First add permission in manifest
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Add image which you want to appear.
chatheadImg = (ImageView)chatheadView.findViewById(R.id.chathead_img);
Then make the service and add window manager to it.
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 0;
params.y = 100;
And just register touch events on view
chatheadView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//To do
break;
case MotionEvent.ACTION_MOVE:
break;
});
check these tutorials for better understanding
http://www.androidhive.info/2016/11/android-floating-widget-like-facebook-chat-head/
https://github.com/henrychuangtw/Android-ChatHead
adding this permission in manifest
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
LayoutInflater layoutInflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutView = layoutInflater.inflate(R.layout.ringing_layout, null);
phoneTv = layoutView.findViewById(R.id.phone);
Log.d("TAG", "showLayout: " + phoneTv );
p = new WindowManager.LayoutParams(
// Shrink the window to wrap the content rather than filling the screen
600, 600,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT);
layoutView.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
windowManager.removeView(layoutView);
}
});
p.gravity = Gravity.CENTER | Gravity.CENTER;
p.x = 0;
p.y = 0;
windowManager.addView(layoutView, p);
Log.d("TAG", "showLayout: ");
I have a LinearLayout, i create(programatically) and adds buttons to it
public void initButtons(){
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
new android.app.ActionBar.LayoutParams(dpiToPixels(context, 100), dpiToPixels(context,100)));
params.setMargins(dpiToPixels(context,20), 0, 0, 0);
button.setLayoutParams(params);
}
layout.addView(button, index);
When user touch(onTouchListener) any button i want to scale it:
button.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) button
.getLayoutParams();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
lp.width = (int) (93 * Resources.getSystem()
.getDisplayMetrics().density);
lp.height = (int) (93 * Resources.getSystem()
.getDisplayMetrics().density);
button.setLayoutParams(lp);
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
lp.width = (int) (100 * Resources.getSystem()
.getDisplayMetrics().density);
lp.height = (int) (100 * Resources.getSystem()
.getDisplayMetrics().density);
button.setLayoutParams(lp);
}
return false;
}
});
The problem is that when i press one button down it affects every other buttons on the LinearLayout( looks scaled and moving a little left). Anyone has any idea how to solve this?
This is bound to happen in a LinearLayout.
Try using FrameLayout. In a frameLayout, changing one button size won't affect others.
You can also use a RelativeLayout, but you shouldn't position your buttons 'relative' to others in it.
Im developing an app where a floating component should appear when the call is received and the component will have several buttons to perform necessary actions.
I have tried the follow.
I implemented a popup window by making the main activity translucent.when this component pops up, Im able to move it on the screen, but since the activity is translucent, im not able to perform any other activity.
here u can see the popup window, i can move it, but i cannot scroll the menudrawer in the background. How can i implement in such a way that i can perform both operations, i.e on popupwindow and the background screen.
My codes
`public class MainActivity extends Activity {
int mCurrentX;
int mCurrentY;
private float mDx;
private float mDy;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
final View cv = new View(this);
TextView tv = new TextView(this);
tv.setBackgroundColor(0xffeeeeee);
tv.setTextColor(0xff000000);
tv.setTextSize(24);
tv.setText("click me\nthen drag me");
tv.setPadding(8, 8, 8, 8);
final PopupWindow mPopup = new PopupWindow(tv, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
OnTouchListener otl = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mDx = mCurrentX - event.getRawX();
mDy = mCurrentY - event.getRawY();
} else
if (action == MotionEvent.ACTION_MOVE) {
mCurrentX = (int) (event.getRawX() + mDx);
mCurrentY = (int) (event.getRawY() + mDy);
mPopup.update(mCurrentX, mCurrentY, -1, -1);
}
return true;
}
};
tv.setOnTouchListener(otl);
mCurrentX = 20;
mCurrentY = 50;
cv.post(new Runnable() {
#Override
public void run() {
mPopup.showAtLocation(cv, Gravity.NO_GRAVITY, mCurrentX, mCurrentY);
}
});
}
}`
manifest
`<application android:label="#string/app_name" android:icon="#drawable/ic_launcher">
<activity android:name=".MainActivity"
android:theme="#android:style/Theme.Translucent.NoTitleBar"
android:label="#string/app_name"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="PopupMainActivity"
android:label="#string/app_name"
android:theme="#style/Theme.FloatingWindow.Popup"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:clearTaskOnLaunch="true"
android:exported="true"
tools:ignore="ExportedActivity" />
</application>`
please help me on this. I want to implement a widget kind of component on the whole. Thanks!!
You have to start a foreground Service and draw using the WindowManager on top of everything else, manage your popup position, size, etc...
WindowManger windowManager = Context.getSystemService(Context.WINDOW_SERVICE);
You will also have to add this permission "android.permission.SYSTEM_ALERT_WINDOW" to your manifest.
A simpler solution would be to use a library called StandOut, it basically takes care of all that I mentioned above and provide extra features like:
Window decorators (titlebar, minimize/close buttons, border, resize handle)
Windows are moveable and resizable. You can bring-to-front, minimize,
and close
Minimized windows can be restored (the example APK demos this using
the notification panel)
Create multiple types of windows, and multiple windows of each type
I have one activity, ExampleActivity
<activity android:name="com.android.ExampleActivity"
android:label="#string/app_name"
android:allowEmbedded="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
With this define in manifiest, round layout is detect without problems.
But with this manifiest, the SAME activity, the SAME code not works.
<activity android:name="com.android.ExampleActivity"
android:label="#string/app_name"
android:allowEmbedded="true">
<meta-data android:name="com.google.android.clockwork.home.preview" android:resource="#drawable/example_watch_background" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" />
</intent-filter>
</activity>
For detect round layout, moto 360 device, using onApplyWindowInsets or onReadyForContent, but the same problem.
Any idea because when i used this category, com.google.android.clockwork.home.category.HOME_BACKGROUND, not works ?
Thanks
With the new SDK, you need to do it like it's shown on Android Wear for Developers
private class Engine extends CanvasWatchFaceService.Engine {
boolean mIsRound;
int mChinSize;
#Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
mIsRound = insets.isRound();
mChinSize = insets.getSystemWindowInsetBottom();
}
...
}
Here, as you can see, you may also get a value of a bottom screen gap (ex. Moto 360).
Until the new andrioid smartwatch SDK is realeased, you can't use setOnApplyWindowInsetsListener / onApplyWindowInsets on custom watch face. This functionality only works on smartwatch app's (without add in manifest).
To know if the clock face is round, you can use:
public static boolean heightSameAsWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
return height == width;
}
private void checkIfWatchIsRound() {
if (heightSameAsWidth(getApplicationContext())) {
isRound = false;
} else {
isRound = true;
}
}