I am trying to implement an app that draws on top of other application using android.permission.SYSTEM_ALERT_WINDOW
. It displays a straight line(ImageView
) on screen that can be dragged(One Finger), zoomed(pinch zoom) and rotated(Two Finger).
I am able to drag the ImageView
on the screen with help from this chatHead code. But I am not able to apply zoom and rotate on the ImageView
on this service but zoom and rotate works perfect when applied on normal activity which extends AppCompatActivity
(Using second answer from this question ).
I am providing my complete code below for reference.
HomeActivity.java
From HomeActivity
I am calling both Service(LineService)
and AppCompatActivity(LineActivity)
. I am not able to implement the function viewTransformation()
from LineActivity
in LineService
since it is a Service
. Is there any other way I can perform these transformations on LineService
.
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
public class HomeActivity extends Activity {
public final static int REQUEST_CODE = 5463&0xffffff00;
Button buttonLineService;
Button buttonLineActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
buttonLineService = findViewById(R.id.buttonLineService);
buttonLineActivity = findViewById(R.id.buttonLineActivity);
checkDrawOverlayPermission();
buttonLineService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(getApplicationContext(), LineService.class);
startService(i);
finish();
}
});
buttonLineActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(getApplicationContext(), LineActivity.class);
startActivity(i);
finish();
}
});
}
public void checkDrawOverlayPermission() {
/** check if we already have permission to draw over other apps */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(HomeActivity.this)) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
// (AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW.equals(op) &&
// packageName.equals(mContext.getPackageName()))) {
/** if not construct intent to request permission */
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/** check if received result code
is equal our requested code for draw permission */
if (requestCode == REQUEST_CODE) {
// ** if so check once again if we have permission */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {
// continue here - permission was granted
}
}
}
}
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="100dp"
android:orientation="vertical"
android:layout_centerHorizontal="true">
<Button
android:id="@+id/buttonPermission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Provide Overlay Permission"/>
<Button
android:id="@+id/buttonLineService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Line Service"/>
<Button
android:id="@+id/buttonLineActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Line Activity"/>
</LinearLayout>
</RelativeLayout>
LineService.java
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
public class LineService extends Service {
private WindowManager windowManager;
private ImageView line;
@Override
public IBinder onBind(Intent intent) {
// Not used
return null;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
line = new ImageView(this);
line.setImageResource(R.drawable.line);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER;
params.x = 0;
params.y = 100;
windowManager.addView(line, params);
line.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getPointerCount() == 2) {
} else {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
windowManager.updateViewLayout(line, params);
return true;
}
}
return false;
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (line != null) windowManager.removeView(line);
}
}
LineActivity.java
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
public class LineActivity extends AppCompatActivity {
private ImageView line;
float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
private boolean isZoomAndRotate;
private boolean isOutSide;
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
private float xCoOrdinate, yCoOrdinate;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_line);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER;
params.x = 0;
params.y = 100;
line = findViewById(R.id.imageview_trash);
line.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
view.bringToFront();
viewTransformation(view, event);
return true;
}
});
}
private void viewTransformation(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
xCoOrdinate = view.getX() - event.getRawX();
yCoOrdinate = view.getY() - event.getRawY();
start.set(event.getX(), event.getY());
isOutSide = false;
mode = DRAG;
lastEvent = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
midPoint(mid, event);
mode = ZOOM;
}
lastEvent = new float[4];
lastEvent[0] = event.getX(0);
lastEvent[1] = event.getX(1);
lastEvent[2] = event.getY(0);
lastEvent[3] = event.getY(1);
d = rotation(event);
break;
case MotionEvent.ACTION_UP:
isZoomAndRotate = false;
if (mode == DRAG) {
float x = event.getX();
float y = event.getY();
}
case MotionEvent.ACTION_OUTSIDE:
isOutSide = true;
mode = NONE;
lastEvent = null;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
lastEvent = null;
break;
case MotionEvent.ACTION_MOVE:
if (!isOutSide) {
if (mode == DRAG) {
isZoomAndRotate = false;
view.animate().x(event.getRawX() + xCoOrdinate).y(event.getRawY() + yCoOrdinate).setDuration(0).start();
}
if (mode == ZOOM && event.getPointerCount() == 2) {
float newDist1 = spacing(event);
if (newDist1 > 10f) {
float scale = newDist1 / oldDist * view.getScaleX();
view.setScaleX(scale);
view.setScaleY(scale);
}
if (lastEvent != null) {
newRot = rotation(event);
view.setRotation((float) (view.getRotation() + (newRot - d)));
}
}
}
break;
}
}
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (int) Math.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
@Override
public void onBackPressed() {
super.onBackPressed();
Intent i = new Intent(getApplicationContext(), HomeActivity.class);
startActivity(i);
finish();
}
}
activity_test_line.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LineActivity">
<ImageView
android:id="@+id/imageview_trash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/line" />
</LinearLayout>
line.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#FFFF0000"
android:endColor="#80FF00FF"
android:angle="45"/>
<padding android:left="200dp"
android:top="200dp"
android:right="200dp"
android:bottom="200dp" />
<corners android:radius="8dp" />
<size android:width="200dp"
android:height="5dp"/>
</shape>