mFloatWindowMap;
+
+ public static IFloatWindow get() {
+ return get(mDefaultTag);
+ }
+
+ public static IFloatWindow get(@NonNull String tag) {
+ return mFloatWindowMap == null ? null : mFloatWindowMap.get(tag);
+ }
+
+ private static B mBuilder = null;
+
+ @MainThread
+ public static B with(@NonNull Context applicationContext) {
+ return mBuilder = new B(applicationContext);
+ }
+
+ public static void destroy() {
+ destroy(mDefaultTag);
+ }
+
+ public static void destroy(String tag) {
+ if (mFloatWindowMap == null || !mFloatWindowMap.containsKey(tag)) {
+ return;
+ }
+ mFloatWindowMap.get(tag).dismiss();
+ mFloatWindowMap.remove(tag);
+ }
+
+ public static class B {
+ Context mApplicationContext;
+ View mView;
+ private int mLayoutId;
+ int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
+ int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
+ int gravity = Gravity.TOP | Gravity.START;
+ int xOffset;
+ int yOffset;
+ boolean mShow = true;
+ Class[] mActivities;
+ int mMoveType = MoveType.slide;
+ int mSlideLeftMargin;
+ int mSlideRightMargin;
+ long mDuration = 300;
+ TimeInterpolator mInterpolator;
+ private String mTag = mDefaultTag;
+ boolean mDesktopShow;
+ PermissionListener mPermissionListener;
+ ViewStateListener mViewStateListener;
+
+ private B() {
+
+ }
+
+ B(Context applicationContext) {
+ mApplicationContext = applicationContext;
+ }
+
+ public B setView(@NonNull View view) {
+ mView = view;
+ return this;
+ }
+
+ public B setView(@LayoutRes int layoutId) {
+ mLayoutId = layoutId;
+ return this;
+ }
+
+ public B setWidth(int width) {
+ mWidth = width;
+ return this;
+ }
+
+ public B setHeight(int height) {
+ mHeight = height;
+ return this;
+ }
+
+ public B setWidth(@Screen.screenType int screenType, float ratio) {
+ mWidth = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ public B setHeight(@Screen.screenType int screenType, float ratio) {
+ mHeight = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ public B setX(int x) {
+ xOffset = x;
+ return this;
+ }
+
+ public B setY(int y) {
+ yOffset = y;
+ return this;
+ }
+
+ public B setX(@Screen.screenType int screenType, float ratio) {
+ xOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+ public B setY(@Screen.screenType int screenType, float ratio) {
+ yOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ /**
+ * 设置 Activity 过滤器,用于指定在哪些界面显示悬浮窗,默认全部界面都显示
+ *
+ * @param show 过滤类型,子类类型也会生效
+ * @param activities 过滤界面
+ */
+ public B setFilter(boolean show, @NonNull Class... activities) {
+ mShow = show;
+ mActivities = activities;
+ return this;
+ }
+
+ public B setMoveType(@MoveType.MOVE_TYPE int moveType) {
+ return setMoveType(moveType, 0, 0);
+ }
+
+
+ /**
+ * 设置带边距的贴边动画,只有 moveType 为 MoveType.slide,设置边距才有意义,这个方法不标准,后面调整
+ *
+ * @param moveType 贴边动画 MoveType.slide
+ * @param slideLeftMargin 贴边动画左边距,默认为 0
+ * @param slideRightMargin 贴边动画右边距,默认为 0
+ */
+ public B setMoveType(@MoveType.MOVE_TYPE int moveType, int slideLeftMargin, int slideRightMargin) {
+ mMoveType = moveType;
+ mSlideLeftMargin = slideLeftMargin;
+ mSlideRightMargin = slideRightMargin;
+ return this;
+ }
+
+ public B setMoveStyle(long duration, @Nullable TimeInterpolator interpolator) {
+ mDuration = duration;
+ mInterpolator = interpolator;
+ return this;
+ }
+
+ public B setTag(@NonNull String tag) {
+ mTag = tag;
+ return this;
+ }
+
+ public B setDesktopShow(boolean show) {
+ mDesktopShow = show;
+ return this;
+ }
+
+ public B setPermissionListener(PermissionListener listener) {
+ mPermissionListener = listener;
+ return this;
+ }
+
+ public B setViewStateListener(ViewStateListener listener) {
+ mViewStateListener = listener;
+ return this;
+ }
+
+ public void build() {
+ if (mFloatWindowMap == null) {
+ mFloatWindowMap = new HashMap<>();
+ }
+ if (mFloatWindowMap.containsKey(mTag)) {
+ throw new IllegalArgumentException("FloatWindow of this tag has been added, Please set a new tag for the new FloatWindow");
+ }
+ if (mView == null && mLayoutId == 0) {
+ throw new IllegalArgumentException("View has not been set!");
+ }
+ if (mView == null) {
+ mView = Util.inflate(mApplicationContext, mLayoutId);
+ }
+ IFloatWindow floatWindowImpl = new IFloatWindowImpl(this);
+ mFloatWindowMap.put(mTag, floatWindowImpl);
+ }
+
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindow.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindow.java
new file mode 100644
index 0000000..18b1d3d
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindow.java
@@ -0,0 +1,32 @@
+package com.yhao.floatwindow;
+
+import android.view.View;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public abstract class IFloatWindow {
+ public abstract void show();
+
+ public abstract void hide();
+
+ public abstract boolean isShowing();
+
+ public abstract int getX();
+
+ public abstract int getY();
+
+ public abstract void updateX(int x);
+
+ public abstract void updateX(@Screen.screenType int screenType,float ratio);
+
+ public abstract void updateY(int y);
+
+ public abstract void updateY(@Screen.screenType int screenType,float ratio);
+
+ public abstract View getView();
+
+ abstract void dismiss();
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindowImpl.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindowImpl.java
new file mode 100644
index 0000000..65f222e
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/IFloatWindowImpl.java
@@ -0,0 +1,302 @@
+package com.yhao.floatwindow;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class IFloatWindowImpl extends IFloatWindow {
+
+
+ private FloatWindow.B mB;
+ private FloatView mFloatView;
+ private FloatLifecycle mFloatLifecycle;
+ private boolean isShow;
+ private boolean once = true;
+ private ValueAnimator mAnimator;
+ private TimeInterpolator mDecelerateInterpolator;
+ private float downX;
+ private float downY;
+ private float upX;
+ private float upY;
+ private boolean mClick = false;
+ private int mSlop;
+
+
+ private IFloatWindowImpl() {
+
+ }
+
+ IFloatWindowImpl(FloatWindow.B b) {
+ mB = b;
+ if (mB.mMoveType == MoveType.fixed) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ mFloatView = new FloatPhone(b.mApplicationContext, mB.mPermissionListener);
+ } else {
+ mFloatView = new FloatToast(b.mApplicationContext);
+ }
+ } else {
+ mFloatView = new FloatPhone(b.mApplicationContext, mB.mPermissionListener);
+ initTouchEvent();
+ }
+ mFloatView.setSize(mB.mWidth, mB.mHeight);
+ mFloatView.setGravity(mB.gravity, mB.xOffset, mB.yOffset);
+ mFloatView.setView(mB.mView);
+ mFloatLifecycle = new FloatLifecycle(mB.mApplicationContext, mB.mShow, mB.mActivities, new LifecycleListener() {
+ @Override
+ public void onShow() {
+ show();
+ }
+
+ @Override
+ public void onHide() {
+ hide();
+ }
+
+ @Override
+ public void onBackToDesktop() {
+ if (!mB.mDesktopShow) {
+ hide();
+ }
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onBackToDesktop();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void show() {
+ if (once) {
+ mFloatView.init();
+ once = false;
+ isShow = true;
+ } else {
+ if (isShow) {
+ return;
+ }
+ getView().setVisibility(View.VISIBLE);
+ isShow = true;
+ }
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onShow();
+ }
+ }
+
+ @Override
+ public void hide() {
+ if (once || !isShow) {
+ return;
+ }
+ getView().setVisibility(View.INVISIBLE);
+ isShow = false;
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onHide();
+ }
+ }
+
+ @Override
+ public boolean isShowing() {
+ return isShow;
+ }
+
+ @Override
+ void dismiss() {
+ mFloatView.dismiss();
+ isShow = false;
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onDismiss();
+ }
+ }
+
+ @Override
+ public void updateX(int x) {
+ checkMoveType();
+ mB.xOffset = x;
+ mFloatView.updateX(x);
+ }
+
+ @Override
+ public void updateY(int y) {
+ checkMoveType();
+ mB.yOffset = y;
+ mFloatView.updateY(y);
+ }
+
+ @Override
+ public void updateX(int screenType, float ratio) {
+ checkMoveType();
+ mB.xOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mB.mApplicationContext) :
+ Util.getScreenHeight(mB.mApplicationContext)) * ratio);
+ mFloatView.updateX(mB.xOffset);
+
+ }
+
+ @Override
+ public void updateY(int screenType, float ratio) {
+ checkMoveType();
+ mB.yOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mB.mApplicationContext) :
+ Util.getScreenHeight(mB.mApplicationContext)) * ratio);
+ mFloatView.updateY(mB.yOffset);
+
+ }
+
+ @Override
+ public int getX() {
+ return mFloatView.getX();
+ }
+
+ @Override
+ public int getY() {
+ return mFloatView.getY();
+ }
+
+
+ @Override
+ public View getView() {
+ mSlop = ViewConfiguration.get(mB.mApplicationContext).getScaledTouchSlop();
+ return mB.mView;
+ }
+
+
+ private void checkMoveType() {
+ if (mB.mMoveType == MoveType.fixed) {
+ throw new IllegalArgumentException("FloatWindow of this tag is not allowed to move!");
+ }
+ }
+
+
+ private void initTouchEvent() {
+ switch (mB.mMoveType) {
+ case MoveType.inactive:
+ break;
+ default:
+ getView().setOnTouchListener(new View.OnTouchListener() {
+ float lastX, lastY, changeX, changeY;
+ int newX, newY;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ downX = event.getRawX();
+ downY = event.getRawY();
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ cancelAnimator();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ changeX = event.getRawX() - lastX;
+ changeY = event.getRawY() - lastY;
+ newX = (int) (mFloatView.getX() + changeX);
+ newY = (int) (mFloatView.getY() + changeY);
+ mFloatView.updateXY(newX, newY);
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onPositionUpdate(newX, newY);
+ }
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ break;
+ case MotionEvent.ACTION_UP:
+ upX = event.getRawX();
+ upY = event.getRawY();
+ mClick = (Math.abs(upX - downX) > mSlop) || (Math.abs(upY - downY) > mSlop);
+ switch (mB.mMoveType) {
+ case MoveType.slide:
+ int startX = mFloatView.getX();
+ int endX = (startX * 2 + v.getWidth() > Util.getScreenWidth(mB.mApplicationContext)) ?
+ Util.getScreenWidth(mB.mApplicationContext) - v.getWidth() - mB.mSlideRightMargin :
+ mB.mSlideLeftMargin;
+ mAnimator = ObjectAnimator.ofInt(startX, endX);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int x = (int) animation.getAnimatedValue();
+ mFloatView.updateX(x);
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onPositionUpdate(x, (int) upY);
+ }
+ }
+ });
+ startAnimator();
+ break;
+ case MoveType.back:
+ PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("x", mFloatView.getX(), mB.xOffset);
+ PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("y", mFloatView.getY(), mB.yOffset);
+ mAnimator = ObjectAnimator.ofPropertyValuesHolder(pvhX, pvhY);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int x = (int) animation.getAnimatedValue("x");
+ int y = (int) animation.getAnimatedValue("y");
+ mFloatView.updateXY(x, y);
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onPositionUpdate(x, y);
+ }
+ }
+ });
+ startAnimator();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return mClick;
+ }
+ });
+ }
+ }
+
+
+ private void startAnimator() {
+ if (mB.mInterpolator == null) {
+ if (mDecelerateInterpolator == null) {
+ mDecelerateInterpolator = new DecelerateInterpolator();
+ }
+ mB.mInterpolator = mDecelerateInterpolator;
+ }
+ mAnimator.setInterpolator(mB.mInterpolator);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator.removeAllUpdateListeners();
+ mAnimator.removeAllListeners();
+ mAnimator = null;
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onMoveAnimEnd();
+ }
+ }
+ });
+ mAnimator.setDuration(mB.mDuration).start();
+ if (mB.mViewStateListener != null) {
+ mB.mViewStateListener.onMoveAnimStart();
+ }
+ }
+
+ private void cancelAnimator() {
+ if (mAnimator != null && mAnimator.isRunning()) {
+ mAnimator.cancel();
+ }
+ }
+
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LifecycleListener.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LifecycleListener.java
new file mode 100644
index 0000000..8796d82
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LifecycleListener.java
@@ -0,0 +1,15 @@
+package com.yhao.floatwindow;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+interface LifecycleListener {
+
+ void onShow();
+
+ void onHide();
+
+ void onBackToDesktop();
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LogUtil.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LogUtil.java
new file mode 100644
index 0000000..ece7e2e
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/LogUtil.java
@@ -0,0 +1,28 @@
+package com.yhao.floatwindow;
+
+import android.util.Log;
+
+
+/**
+ * Created by yhao on 2017/12/29.
+ * https://github.com/yhaolpz
+ */
+
+class LogUtil {
+
+ private static final String TAG = "FloatWindow";
+
+
+ static void e(String message) {
+
+ Log.e(TAG, message);
+ }
+
+
+ static void d(String message) {
+
+ Log.d(TAG, message);
+ }
+
+
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Miui.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Miui.java
new file mode 100644
index 0000000..0e93211
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Miui.java
@@ -0,0 +1,190 @@
+package com.yhao.floatwindow;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.yhao.floatwindow.Rom.isIntentAvailable;
+
+/**
+ * Created by yhao on 2017/12/30.
+ * https://github.com/yhaolpz
+ *
+ * 需要清楚:一个MIUI版本对应小米各种机型,基于不同的安卓版本,但是权限设置页跟MIUI版本有关
+ * 测试TYPE_TOAST类型:
+ * 7.0:
+ * 小米 5 MIUI8 -------------------- 失败
+ * 小米 Note2 MIUI9 -------------------- 失败
+ * 6.0.1
+ * 小米 5 -------------------- 失败
+ * 小米 红米note3 -------------------- 失败
+ * 6.0:
+ * 小米 5 -------------------- 成功
+ * 小米 红米4A MIUI8 -------------------- 成功
+ * 小米 红米Pro MIUI7 -------------------- 成功
+ * 小米 红米Note4 MIUI8 -------------------- 失败
+ *
+ * 经过各种横向纵向测试对比,得出一个结论,就是小米对TYPE_TOAST的处理机制毫无规律可言!
+ * 跟Android版本无关,跟MIUI版本无关,addView方法也不报错
+ * 所以最后对小米6.0以上的适配方法是:不使用 TYPE_TOAST 类型,统一申请权限
+ */
+
+class Miui {
+
+ private static final String miui = "ro.miui.ui.version.name";
+ private static final String miui5 = "V5";
+ private static final String miui6 = "V6";
+ private static final String miui7 = "V7";
+ private static final String miui8 = "V8";
+ private static final String miui9 = "V9";
+ private static List mPermissionListenerList;
+ private static PermissionListener mPermissionListener;
+
+
+ static boolean rom() {
+ LogUtil.d(" Miui : " + Miui.getProp());
+ return Build.MANUFACTURER.equals("Xiaomi");
+ }
+
+ private static String getProp() {
+ return Rom.getProp(miui);
+ }
+
+ /**
+ * Android6.0以下申请权限
+ */
+ static void req(final Context context, PermissionListener permissionListener) {
+ if (PermissionUtil.hasPermission(context)) {
+ permissionListener.onSuccess();
+ return;
+ }
+ if (mPermissionListenerList == null) {
+ mPermissionListenerList = new ArrayList<>();
+ mPermissionListener = new PermissionListener() {
+ @Override
+ public void onSuccess() {
+ for (PermissionListener listener : mPermissionListenerList) {
+ listener.onSuccess();
+ }
+ mPermissionListenerList.clear();
+ }
+ @Override
+ public void onFail() {
+ for (PermissionListener listener : mPermissionListenerList) {
+ listener.onFail();
+ }
+ mPermissionListenerList.clear();
+ }
+ };
+ req_(context);
+ }
+ mPermissionListenerList.add(permissionListener);
+ }
+
+
+ private static void req_(final Context context) {
+ switch (getProp()) {
+ case miui5:
+ reqForMiui5(context);
+ break;
+ case miui6:
+ case miui7:
+ reqForMiui67(context);
+ break;
+ case miui8:
+ case miui9:
+ reqForMiui89(context);
+ break;
+ }
+ FloatLifecycle.setResumedListener(new ResumedListener() {
+ @Override
+ public void onResumed() {
+ if (PermissionUtil.hasPermission(context)) {
+ mPermissionListener.onSuccess();
+ } else {
+ mPermissionListener.onFail();
+ }
+ }
+ });
+ }
+
+
+ private static void reqForMiui5(Context context) {
+ String packageName = context.getPackageName();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", packageName, null);
+ intent.setData(uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (isIntentAvailable(intent, context)) {
+ context.startActivity(intent);
+ } else {
+ LogUtil.e("intent is not available!");
+ }
+ }
+
+ private static void reqForMiui67(Context context) {
+ Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+ intent.setClassName("com.miui.securitycenter",
+ "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
+ intent.putExtra("extra_pkgname", context.getPackageName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (isIntentAvailable(intent, context)) {
+ context.startActivity(intent);
+ } else {
+ LogUtil.e("intent is not available!");
+ }
+ }
+
+ private static void reqForMiui89(Context context) {
+ Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+ intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
+ intent.putExtra("extra_pkgname", context.getPackageName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (isIntentAvailable(intent, context)) {
+ context.startActivity(intent);
+ } else {
+ intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
+ intent.setPackage("com.miui.securitycenter");
+ intent.putExtra("extra_pkgname", context.getPackageName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (isIntentAvailable(intent, context)) {
+ context.startActivity(intent);
+ } else {
+ LogUtil.e("intent is not available!");
+ }
+ }
+ }
+
+
+ /**
+ * 有些机型在添加TYPE-TOAST类型时会自动改为TYPE_SYSTEM_ALERT,通过此方法可以屏蔽修改
+ * 但是...即使成功显示出悬浮窗,移动的话也会崩溃
+ */
+ private static void addViewToWindow(WindowManager wm, View view, WindowManager.LayoutParams params) {
+ setMiUI_International(true);
+ wm.addView(view, params);
+ setMiUI_International(false);
+ }
+
+
+ private static void setMiUI_International(boolean flag) {
+ try {
+ Class BuildForMi = Class.forName("miui.os.Build");
+ Field isInternational = BuildForMi.getDeclaredField("IS_INTERNATIONAL_BUILD");
+ isInternational.setAccessible(true);
+ isInternational.setBoolean(null, flag);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/MoveType.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/MoveType.java
new file mode 100644
index 0000000..eccad1c
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/MoveType.java
@@ -0,0 +1,24 @@
+package com.yhao.floatwindow;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class MoveType {
+ static final int fixed = 0;
+ public static final int inactive = 1;
+ public static final int active = 2;
+ public static final int slide = 3;
+ public static final int back = 4;
+
+ @IntDef({fixed, inactive, active, slide, back})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MOVE_TYPE {
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionListener.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionListener.java
new file mode 100644
index 0000000..56e23db
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionListener.java
@@ -0,0 +1,11 @@
+package com.yhao.floatwindow;
+
+/**
+ * Created by yhao on 2017/11/14.
+ * https://github.com/yhaolpz
+ */
+public interface PermissionListener {
+ void onSuccess();
+
+ void onFail();
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionUtil.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionUtil.java
new file mode 100644
index 0000000..e6e25fb
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/PermissionUtil.java
@@ -0,0 +1,85 @@
+package com.yhao.floatwindow;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.annotation.RequiresApi;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.lang.reflect.Method;
+
+/**
+ * Created by yhao on 2017/12/29.
+ * https://github.com/yhaolpz
+ */
+
+class PermissionUtil {
+
+ static boolean hasPermission(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return Settings.canDrawOverlays(context);
+ } else {
+ return hasPermissionBelowMarshmallow(context);
+ }
+ }
+
+ static boolean hasPermissionOnActivityResult(Context context) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
+ return hasPermissionForO(context);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return Settings.canDrawOverlays(context);
+ } else {
+ return hasPermissionBelowMarshmallow(context);
+ }
+ }
+
+ /**
+ * 6.0以下判断是否有权限
+ * 理论上6.0以上才需处理权限,但有的国内rom在6.0以下就添加了权限
+ * 其实此方式也可以用于判断6.0以上版本,只不过有更简单的canDrawOverlays代替
+ */
+ static boolean hasPermissionBelowMarshmallow(Context context) {
+ try {
+ AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ Method dispatchMethod = AppOpsManager.class.getMethod("checkOp", int.class, int.class, String.class);
+ //AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
+ return AppOpsManager.MODE_ALLOWED == (Integer) dispatchMethod.invoke(
+ manager, 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * 用于判断8.0时是否有权限,仅用于OnActivityResult
+ * 针对8.0官方bug:在用户授予权限后Settings.canDrawOverlays或checkOp方法判断仍然返回false
+ */
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ private static boolean hasPermissionForO(Context context) {
+ try {
+ WindowManager mgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ if (mgr == null) return false;
+ View viewToAdd = new View(context);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(0, 0,
+ android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ viewToAdd.setLayoutParams(params);
+ mgr.addView(viewToAdd, params);
+ mgr.removeView(viewToAdd);
+ return true;
+ } catch (Exception e) {
+ LogUtil.e("hasPermissionForO e:" + e.toString());
+ }
+ return false;
+ }
+
+
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ResumedListener.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ResumedListener.java
new file mode 100644
index 0000000..386460e
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ResumedListener.java
@@ -0,0 +1,10 @@
+package com.yhao.floatwindow;
+
+/**
+ * Created by yhao on 2017/12/30.
+ * https://github.com/yhaolpz
+ */
+
+interface ResumedListener {
+ void onResumed();
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Rom.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Rom.java
new file mode 100644
index 0000000..e91fbe8
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Rom.java
@@ -0,0 +1,44 @@
+package com.yhao.floatwindow;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Created by yhao on 2017/12/30.
+ * https://github.com/yhaolpz
+ */
+
+class Rom {
+
+ static boolean isIntentAvailable(Intent intent, Context context) {
+ return intent != null && context.getPackageManager().queryIntentActivities(
+ intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+
+
+ static String getProp(String name) {
+ BufferedReader input = null;
+ try {
+ Process p = Runtime.getRuntime().exec("getprop " + name);
+ input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
+ String line = input.readLine();
+ input.close();
+ return line;
+ } catch (IOException ex) {
+ return null;
+ } finally {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Screen.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Screen.java
new file mode 100644
index 0000000..9bf687c
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Screen.java
@@ -0,0 +1,21 @@
+package com.yhao.floatwindow;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by yhao on 2017/12/23.
+ * https://github.com/yhaolpz
+ */
+
+public class Screen {
+ public static final int width = 0;
+ public static final int height = 1;
+
+ @IntDef({width, height})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface screenType {
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Util.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Util.java
new file mode 100644
index 0000000..585117b
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/Util.java
@@ -0,0 +1,52 @@
+package com.yhao.floatwindow;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.annotation.RequiresApi;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.lang.reflect.Method;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+class Util {
+
+
+ static View inflate(Context applicationContext, int layoutId) {
+ LayoutInflater inflate = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return inflate.inflate(layoutId, null);
+ }
+
+ private static Point sPoint;
+
+ static int getScreenWidth(Context context) {
+ if (sPoint == null) {
+ sPoint = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(sPoint);
+ }
+ return sPoint.x;
+ }
+
+ static int getScreenHeight(Context context) {
+ if (sPoint == null) {
+ sPoint = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(sPoint);
+ }
+ return sPoint.y;
+ }
+
+ static boolean isViewVisible(View view) {
+ return view.getGlobalVisibleRect(new Rect());
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListener.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListener.java
new file mode 100644
index 0000000..b2b4992
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListener.java
@@ -0,0 +1,21 @@
+package com.yhao.floatwindow;
+
+/**
+ * Created by yhao on 2018/5/5
+ * https://github.com/yhaolpz
+ */
+public interface ViewStateListener {
+ void onPositionUpdate(int x, int y);
+
+ void onShow();
+
+ void onHide();
+
+ void onDismiss();
+
+ void onMoveAnimStart();
+
+ void onMoveAnimEnd();
+
+ void onBackToDesktop();
+}
diff --git a/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListenerAdapter.java b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListenerAdapter.java
new file mode 100644
index 0000000..a0b1e3a
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/java/com/yhao/floatwindow/ViewStateListenerAdapter.java
@@ -0,0 +1,42 @@
+package com.yhao.floatwindow;
+
+/**
+ * Created by yhao on 2018/5/5.
+ * https://github.com/yhaolpz
+ */
+public class ViewStateListenerAdapter implements ViewStateListener{
+ @Override
+ public void onPositionUpdate(int x, int y) {
+
+ }
+
+ @Override
+ public void onShow() {
+
+ }
+
+ @Override
+ public void onHide() {
+
+ }
+
+ @Override
+ public void onDismiss() {
+
+ }
+
+ @Override
+ public void onMoveAnimStart() {
+
+ }
+
+ @Override
+ public void onMoveAnimEnd() {
+
+ }
+
+ @Override
+ public void onBackToDesktop() {
+
+ }
+}
diff --git a/src/MyApplication/floatwindow/src/main/res/values/strings.xml b/src/MyApplication/floatwindow/src/main/res/values/strings.xml
new file mode 100644
index 0000000..62ef45f
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ FFWindow
+
diff --git a/src/MyApplication/floatwindow/src/main/res/values/style.xml b/src/MyApplication/floatwindow/src/main/res/values/style.xml
new file mode 100644
index 0000000..6b0f3ca
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/main/res/values/style.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/MyApplication/floatwindow/src/test/java/com/example/fixedfloatwindow/ExampleUnitTest.java b/src/MyApplication/floatwindow/src/test/java/com/example/fixedfloatwindow/ExampleUnitTest.java
new file mode 100644
index 0000000..636b835
--- /dev/null
+++ b/src/MyApplication/floatwindow/src/test/java/com/example/fixedfloatwindow/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.fixedfloatwindow;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/src/MyApplication/settings.gradle b/src/MyApplication/settings.gradle
index f7e3ac0..9da7822 100644
--- a/src/MyApplication/settings.gradle
+++ b/src/MyApplication/settings.gradle
@@ -1,2 +1 @@
-include ':app', ':my_library'
-project(':my_library').projectDir = new File('library')
\ No newline at end of file
+include ':app', ':floatwindow'