当前位置: 游戏平台 > 互联网科技 > 正文

One plus TYPE_TOAST 悬浮窗无效的缘由

时间:2020-02-15 06:02来源:互联网科技
Android中的悬浮窗彰显是三个特别费劲的主题材料,互连网早就有过多消除方案了,大概归为下边两类: 那篇博客主要介绍的是 Android主流种种机型和各个版本的飘浮窗权限适配,不过出

Android中的悬浮窗彰显是三个特别费劲的主题材料,互连网早就有过多消除方案了,大概归为下边两类:

那篇博客主要介绍的是 Android 主流种种机型和各个版本的飘浮窗权限适配,不过出于碎片化的主题材料,所以在适配方面也回天无力完结完全的主流机型适配,那几个供给大家的多头使劲,这么些博客的名字永恒都以三个现在时,感兴趣或然找到其余机型适配方法的请留言告知小编,只怕加群544645972一齐交流一下,特别多谢~ 相关权限请看作者的另大器晚成篇博客:android permission权限与安全部制解析,或然有关权限的案例使用:android WindowManager深入分析与骗取QQ密码案例深入分析,还会有录音和录制头权限的适配:Android 音像头权限适配。 转发请表明出处: 源码会实时更新在 gitHub 上,不会实时更新博客,所以想要看最新代码的同班,请间接去 github 页面查看 markdown。

转发请注解出处:Android悬浮窗权限适配

  1. 设置WindowManager.LayoutParams.type = TYPE_SYSTEM_ALERT,并指引客户张开悬浮窗权限。这种方式首要的困难在于带领客商跳转权限设置页面,由于各商家定制的标题,需求针对广大装置进行对应的适配,近日本来就有大神计算了有的机型的适配难题,详细的情况参见:《Android 悬浮窗权限各机型各系统适配大全》

  2. 设置WindowManager.LayoutParams.type = TYPE_TOAST,能够绕过系统权限检查,但是这种格局的主题材料在于:

    • 悬浮窗在API 18及以下的体系不能吸取Touch事件。《Android悬浮窗TYPE_TOAST小结: 源码深入分析》
    • API 第25中学不能同一时候存在八个Toast类型的悬浮窗,API 25之上系统一直防止客商选用TYPE_TOAST创造悬浮窗。《Android7.1.1对Type Toast的界定》
    • MiUI 第88中学期维校勘了WindowManager中的代码,招致在该系统上任然不可能展现出悬浮窗。

悬浮窗适配有二种办法:第风华正茂种是根据典型的流程,倘若系统并未有授予 APP弹出悬浮窗的权杖,就先跳转到权限授权界面,等客商展开该权限之后,再去弹出悬浮窗,举个例子QQ 等局地主流应用便是这么做得;第三种正是接收系统的狐狸尾巴,绕过权力的申请,轻巧冷酷,这种艺术本人不是特意提出,可是今后相似有个别应用正是这么,比方UC 和有道字典,那样适配在大相当多部手提式有线电话机上都以 OK 的,不过在某个非正规的机型不行,比如某米的 miui8。

悬浮窗相信大家都不素不相识,比如360部手提式无线电话机卫士的加快球,录制应用的小窗,可以侵夺超少的空间,又能维系跟客商的互相。悬浮窗能够透过WindowManager.addView增添。具体用法能够看Android悬浮窗用法计算,根据那篇小说增加过悬浮窗之后,会意识有的手提式有线电话机上海展览中心示不出去,那正是权力的主题素材了。

本文就针对MiUI 8上的题目开展深入分析。

在 4.4~5.1.1 版本之间,和 6.0~最新版本之间的适配方法是不生龙活虎致的,早先的本子由于 google 并未有对这些权力实行单独管理,所以是各家手提式有线电电话机商家根据必要定制的,所以各样权限的授权界面都各不平等,适配起来难度比较大,6.0 之后适配起来就相对轻松非常多了。

悬浮窗权限

使用 type 值为 WindowManager.LayoutParams.TYPE_PHONE 或 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 需求报名 android.permission.SYSTEM_ALERT_WINDOW 权限。

  • API >=23,需求在manifest中申请权限,并在每便必要用到权力的时等候检查查是或不是原来就有该权限,因为客户随即能够收回掉。具体能够看
    Android 6.0 运转时权限管理完全解析
    Android M 权限最好实践
    检查实验方法:调用系统Settings.canDrawOverlays
  • API >=18,只须要在manifest中申请权限。
    对于日常手提式有线电话机,manifest中声称的权杖在设置的时候会暗中认可授权,并无需管理。
    对于有些rom,必要用AppOpsManager.checkOp方法检查测量试验。
  • API <18,暗许有悬浮窗权限,无需处理。

type 值为 WindowManager.LayoutParams.TYPE_TOAST 没有必要权限。

  • API >25,TYPE_TOAST 已经被Google裁断了,会产出活动消失的事态,具体看 这里
  • API >= 19 ,能够吸取触摸和按钮事件。
  • API <19,不能选拔不可能选用触摸和开关事件,无需权限和无法选取触摸事件的源码深入分析:Android悬浮窗TYPE_TOAST小结源码解析
    事前有人做过绕过权力显示悬浮窗,正是用的TYPE_TOAST ,不引入应用这种方法。
    Android无需权限展现悬浮窗, 兼谈逆向深入分析app
    Android 悬浮窗的下结论

在实际的利用进度中我们开采,系统的Toast可以健康呈现,富含自定义的Toast,而通过WindowManger.addView添加的TYPE_TOAST的力不能及展现。咱们驾驭系统Toast实际也是通过调用WindowManger.addView创建的,那么我们得以试着模拟系统创造Toast时选拔的参数,上边是在MiUI 8上开创系统Toast时的LayoutParams

Android 4.4 ~ Android 5.1.1##

是因为剖断权限的类 AppOpsManager 是 API19 版本加多,所以Android 4.4 从前的本子就不用去判别了,直接调用 WindowManager 的 addView 方法弹出就能够,不过日常常有个别独具匠心的无绳电话机厂家在 API19 版本早先就曾经自定义了悬浮窗权限,尽管有开采的,请联系小编。 人所共知,荣耀手提式有线话机的系列实乃过于丰盛,何况叁个品牌的不等版本还应该有不均等的适配方法,举例某米,所以本身在实际上适配的长河中计算了两种通用的点子, 大家能够参照一下:<ul><li>直接百度时而,寻找关键词“OPPO手提式有线电话机悬浮窗适配”等;</li><li>看看 QQ 可能其余的大集团 APP是或不是业已适配,假若已经适配,跳转到相关权限授权页面之后,或许自身能力所能达到直接在装置里找到悬浮窗权限授权页面也是一个道理,使用 adb shell dumpsys activity 命令,找到有关的消息,如下图所示

图片 1那边写图片描述能够清楚看出授权 activity 页面包车型客车包名和 activity 名,并且可以精晓地精晓跳转的 intent 是或不是带了 extra,若无 extra 就能够一向跳入,假诺带上了 extra,百度时而该 activity 的名字,看可以还是不可以找到有用消息,举个例子适配方案照旧源码 APK 之类的;</li><li>依然使用方面包车型大巴点子,找到 activity 的名字,然后 root 计划适配的手机,直接在相关目录 /system/app 下把源码 APK 拷贝出来,反编写翻译,依据 activity 的名字找到相关代码,之后的职业就归纳了;</li><li>还会有多个格局正是发摄人心魄力财富去找,看看已经适配该手机机型的 app 公司是还是不是有和好认知的人,可能干脆点,直接找这么些手提式无线电话机杂货店内部是不是有温馨认知的线上支付朋友,直接询问,方便连忙。</li></ul>

出于 6.0 早先的本子常规手机并未把悬浮窗权限单独拿出来,所以正常情状下是足以平昔动用 WindowManager.addView 方法直接弹出悬浮窗。 怎么样推断手提式有线电话机的机型,办法比超级多,在这里间小编就不贴代码了,平日景色下在 terminal 中实施 getprop 命令,然后在打字与印刷出来的音信中找到有关的机型音信就能够,这里贴出国产三款数见不鲜机型的判别:

/** * 获取 emui 版本号 * @return */public static double getEmuiVersion() { try { String emuiVersion = getSystemProperty("ro.build.version.emui"); String version = emuiVersion.substring(emuiVersion.indexOf; return Double.parseDouble; } catch (Exception e) { e.printStackTrace(); } return 4.0;}/** * 获取小米 rom 版本号,获取失败返回 -1 * * @return miui rom version code, if fail , return -1 */public static int getMiuiVersion() { String version = getSystemProperty("ro.miui.ui.version.name"); if (version != null) { try { return Integer.parseInt(version.substring; } catch (Exception e) { Log.e(TAG, "get miui version code error, version : " + version); } } return -1;}public static String getSystemProperty(String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream; line = input.readLine(); input.close(); } catch (IOException ex) { Log.e(TAG, "Unable to read sysprop " + propName, ex); return null; } finally { if (input != null) { try { input.close(); } catch (IOException e) { Log.e(TAG, "Exception while closing InputStream", e); } } } return line;}public static boolean checkIsHuaweiRom() { return Build.MANUFACTURER.contains;}/** * check if is miui ROM */public static boolean checkIsMiuiRom() { return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name"));}public static boolean checkIsMeizuRom() { //return Build.MANUFACTURER.contains; String meizuFlymeOSFlag = getSystemProperty("ro.build.display.id"); if (TextUtils.isEmpty(meizuFlymeOSFlag)){ return false; }else if (meizuFlymeOSFlag.contains || meizuFlymeOSFlag.toLowerCase().contains{ return true; }else { return false; }}/** * check if is 360 ROM */public static boolean checkIs360Rom() { return Build.MANUFACTURER.contains;}

第意气风发须要适配的就应该是酷派了,而且相比较费心的政工是,miui 的各类版本适配方法都以不相近的,所以只可以每一种版本去单独适配,可是辛亏由于选拔的食指多,网络的素材也正如全。首先第一步当然是决断是还是不是付与了悬浮窗权限,那个时候就须要利用到 AppOpsManager 那个类了,它在那之中有三个 checkop 方法:

/** * Do a quick check for whether an application might be able to perform an operation. * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)} * or {@link #startOp(int, int, String)} for your actual security checks, which also * ensure that the given uid and package name are consistent. This function can just be * used for a quick check to see if an operation has been disabled for the application, * as an early reject of some work. This does not modify the time stamp or other data * about the operation. * @param op The operation to check. One of the OP_* constants. * @param uid The user id of the application attempting to perform the operation. * @param packageName The name of the application attempting to perform the operation. * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without * causing the app to crash). * @throws SecurityException If the app has been configured to crash on this op. * @hide */public int checkOp(int op, int uid, String packageName) { try { int mode = mService.checkOperation(op, uid, packageName); if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } return mode; } catch (RemoteException e) { } return MODE_IGNORED;}

找到悬浮窗权限的 op 值是:

/** @hide */public static final int OP_SYSTEM_ALERT_WINDOW = 24;

静心到这一个函数和那么些值其实都以 hide 的,所以无法,你懂的,只好用反射:

/** * 检测 miui 悬浮窗权限 */public static boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; } else {// if ((context.getApplicationInfo().flags & 1 << 27) == 1) {// return true;// } else {// return false;// } return true; }}@TargetApi(Build.VERSION_CODES.KITKAT)private static boolean checkOp(Context context, int op) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Class clazz = AppOpsManager.class; Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); return AppOpsManager.MODE_ALLOWED == method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName; } catch (Exception e) { Log.e(TAG, Log.getStackTraceString; } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false;}

检查实验达成未来正是跳转到授权页面去开启权限了,然则出于 miui 分歧版本的权杖授权页面不平等,所以须要依照差异版本实行分化管理:

/** * 获取小米 rom 版本号,获取失败返回 -1 * * @return miui rom version code, if fail , return -1 */public static int getMiuiVersion() { String version = RomUtils.getSystemProperty("ro.miui.ui.version.name"); if (version != null) { try { return Integer.parseInt(version.substring; } catch (Exception e) { Log.e(TAG, "get miui version code error, version : " + version); Log.e(TAG, Log.getStackTraceString; } } return -1;}/** * 小米 ROM 权限申请 */public static void applyMiuiPermission(Context context) { int versionCode = getMiuiVersion(); if (versionCode == 5) { goToMiuiPermissionActivity_V5; } else if (versionCode == 6) { goToMiuiPermissionActivity_V6; } else if (versionCode == 7) { goToMiuiPermissionActivity_V7; } else if (versionCode == 8) { goToMiuiPermissionActivity_V8; } else { Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode); }}private static boolean isIntentAvailable(Intent intent, Context context) { if (intent == null) { return false; } return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;}/** * 小米 V5 版本 ROM权限申请 */public static void goToMiuiPermissionActivity_V5(Context context) { Intent intent = null; String packageName = context.getPackageName(); intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package" , packageName, null); intent.setData; intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (isIntentAvailable(intent, context)) { context.startActivity; } else { Log.e(TAG, "intent is not available!"); } //设置页面在应用详情页面// Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");// PackageInfo pInfo = null;// try {// pInfo = context.getPackageManager().getPackageInfo// (HostInterfaceManager.getHostInterface().getApp().getPackageName;// } catch (PackageManager.NameNotFoundException e) {// AVLogUtils.e(TAG, e.getMessage;// }// intent.setClassName("com.android.settings", "com.miui.securitycenter.permission.AppPermissionsEditor");// intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// if (isIntentAvailable(intent, context)) {// context.startActivity;// } else {// AVLogUtils.e(TAG, "Intent is not available!");// }}/** * 小米 V6 版本 ROM权限申请 */public static void goToMiuiPermissionActivity_V6(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; } else { Log.e(TAG, "Intent is not available!"); }}/** * 小米 V7 版本 ROM权限申请 */public static void goToMiuiPermissionActivity_V7(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; } else { Log.e(TAG, "Intent is not available!"); }}/** * 小米 V8 版本 ROM权限申请 */public static void goToMiuiPermissionActivity_V8(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; } else { Log.e(TAG, "Intent is not available!"); }}

getSystemProperty 方法是间接调用 getprop 方法来博取系统消息:

public static String getSystemProperty(String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream; line = input.readLine(); input.close(); } catch (IOException ex) { Log.e(TAG, "Unable to read sysprop " + propName, ex); return null; } finally { if (input != null) { try { input.close(); } catch (IOException e) { Log.e(TAG, "Exception while closing InputStream", e); } } } return line;}

新型的 V8 版本有个别机型已是 6.0 ,所以正是底下介绍到 6.0 的适配方法了,感激 @pinocchio2mx 的举报,有个别机型的 miui8 版本仍然5.1.1,所以 miui8 还是必要做适配,特别多谢,希望大家一同多多反馈难题,多谢~~。

华为的适配,由于笔者司一加的机器相对比较少,所以只适配了 flyme5.1.1/android 5.1.1 版本 mx4 pro 的种类。和HUAWEI相同,首先也要通过 API19 版本增加的 AppOpsManager 类判别是不是予以了权力:

/** * 检测 meizu 悬浮窗权限 */public static boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; } return true;}@TargetApi(Build.VERSION_CODES.KITKAT)private static boolean checkOp(Context context, int op) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Class clazz = AppOpsManager.class; Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); return AppOpsManager.MODE_ALLOWED == method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName; } catch (Exception e) { Log.e(TAG, Log.getStackTraceString; } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false;}

然后是跳转去悬浮窗权限付与分界面:

/** * 去魅族权限申请页面 */public static void applyPermission(Context context){ Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity"); intent.putExtra("packageName", context.getPackageName; intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity;}

假使有Motorola别的版本的适配方案,请联系自身。

摩Toro拉的适配是基于网络找的方案,外加本人的有的优化而成,但是出于华为手提式有线电话机的成都百货上千机型,所以覆盖的机型和系统版本还不是那么完美,即使有其余机型和本子的适配方案,请联系自个儿,小编更新到 github 上。和中兴,一加相仿,首先通过 AppOpsManager 来决断权限是还是不是已经授权:

/** * 检测 Huawei 悬浮窗权限 */public static boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; } return true;}@TargetApi(Build.VERSION_CODES.KITKAT)private static boolean checkOp(Context context, int op) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Class clazz = AppOpsManager.class; Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); return AppOpsManager.MODE_ALLOWED ==  method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName; } catch (Exception e) { Log.e(TAG, Log.getStackTraceString; } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false;}

接下来根据差别的机型和本子跳转到差异的页面:

/** * 去华为权限申请页面 */public static void applyPermission(Context context) { try { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理// ComponentName comp = new ComponentName("com.huawei.systemmanager",// "com.huawei.permissionmanager.ui.SingleAppActivity");//华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决 ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面 intent.setComponent; if (RomUtils.getEmuiVersion { //emui 3.1 的适配 context.startActivity; } else { //emui 3.0 的适配 comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//悬浮窗管理页面 intent.setComponent; context.startActivity; } } catch (SecurityException e) { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理 ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");//华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面 intent.setComponent; context.startActivity; Log.e(TAG, Log.getStackTraceString; } catch (ActivityNotFoundException e) { /** * 手机管家版本较低 HUAWEI SC-UL10 */// Toast.makeText(MainActivity.this, "act找不到", Toast.LENGTH_LONG).show(); Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");//权限管理页面 android4.4// ComponentName comp = new ComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决 intent.setComponent; context.startActivity; e.printStackTrace(); Log.e(TAG, Log.getStackTraceString; } catch (Exception e) { //抛出异常时提示信息 Toast.makeText(context, "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show(); Log.e(TAG, Log.getStackTraceString; }}

emui4 之后正是 6.0 版本了,依照上面介绍的 6.0 适配方案就能够。

360有线电话的适配方案在网络能够找到的资料比较少,唯生龙活虎能够找到的正是这篇:奇酷360 手提式有线电电话机中怎么跳转安全为重中钦赐包名App的权杖管理页面,不过博客中也向来不付诸最终的适配方案,然而最终仍然间接用最轻便易行的主意就能够跳进去了,首先是权力的检查测试:

/** * 检测 360 悬浮窗权限 */public static boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; } return true;}@TargetApi(Build.VERSION_CODES.KITKAT)private static boolean checkOp(Context context, int op) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Class clazz = AppOpsManager.class; Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class); return AppOpsManager.MODE_ALLOWED == method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName; } catch (Exception e) { Log.e(TAG, Log.getStackTraceString; } } else { Log.e("", "Below API 19 cannot invoke!"); } return false;}

一旦没有授予悬浮窗权限,就跳转去权限付与分界面:

public static void applyPermission(Context context) { Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity;}

哈哈,是或不是超级轻便,不时候真相往往一点也不复杂,OK,适配达成。

检查测量检验有个别rom的上浮窗权限

到那边,就清楚大家只必要管理18<=API<23下一些rom的权位。
缘何某个rom那么独特呢?
因为在API 18,Google新添了二个函数AppOpsManager,可是在此个本子,该函数是隐身的 (Android 4.3 隐蔽效用 App Ops 分析State of Qatar,到API 19才通晓。用这些函数能够对manifest申请的权力做生龙活虎层限定,于是就有了360部手提式有线话机卫士,黑莓安全基本。。。

检查评定这一个rom的权杖,方法是均等的,能够通过反射使用AppOpsManager.checkOp

private static final int OP_SYSTEM_ALERT_WINDOW = 24;

boolean isPermitted = checkOp(context, OP_SYSTEM_ALERT_WINDOW);

private static boolean checkOp(Context context, int op) {
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        try {
            Method method = AppOpsManager.class.getDeclaredMethod("checkOp", int.class, int.class, String.class);
            return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
        return false;
    }

图片 2系统Toast其中flags的值(首要照旧为了挡住事件)

Android 6.0 及现在版本##

自己在博客android permission权限与安全部制深入解析- SYSTEM_ALERT_WINDOW中早就介绍到了适配方案,悬浮窗权限在 6.0 之后就被 google 单独拿出来管理了,好处正是对大家来讲适配就足够便于了,在装有手提式有线电电话机和 6.0 以至之后的本子上适配的章程都以意气风发致的,首先要在 Manifest 中静态申请<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />权力,然后在采取时先判断该权限是还是不是早就被授权,如果未有授权使用上边这段代码举行动态申请:

private static final int REQUEST_CODE = 1;//判断权限private boolean commonROMPermissionCheck(Context context) { Boolean result = true; if (Build.VERSION.SDK_INT >= 23) { try { Class clazz = Settings.class; Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class); result =  canDrawOverlays.invoke(null, context); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString; } } return result;}//申请权限private void requestAlertWindowPermission() { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName; startActivityForResult(intent, REQUEST_CODE);}@Override//处理回调protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE) { if (Settings.canDrawOverlays { Log.i(LOGTAG, "onActivityResult granted"); } }}

上述代码需求注意的是:<ul><li>使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION 运转隐式Intent;</li><li>使用 “package:” + getPackageName(卡塔尔国指导App的包名音信;</li><li>使用 Settings.canDrawOverlays 方法判别授权结果。</li></ul>在客户张开相关权限之后本领使用 WindowManager.LayoutParams.TYPE_SYSTEM_E奥迪Q7ROLX570 ,要不然是会一贯崩溃的啊。

怎么绕过系统的权位检查,直接弹出悬浮窗?android WindowManager拆解解析与骗取QQ密码案例剖析那篇博客中自己早就指明出来了,必要使用mParams.type = WindowManager.LayoutParams.TYPE_TOAST; 来取代 mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;,这样就可以达到规定的标准不报名权限,而平素弹出悬浮窗,至于原因嘛,大家看看 PhoneWindowManager 源码的关键处:

@Overridepublic int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) { .... switch  { case TYPE_TOAST: // XXX right now the app process has complete control over // this... should introduce a token to let the system // monitor/control what they are doing. outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW; break; case TYPE_DREAM: case TYPE_INPUT_METHOD: case TYPE_WALLPAPER: case TYPE_PRIVATE_PRESENTATION: case TYPE_VOICE_INTERACTION: case TYPE_ACCESSIBILITY_OVERLAY: // The window manager will check these. break; case TYPE_PHONE: case TYPE_PRIORITY_PHONE: case TYPE_SYSTEM_ALERT: case TYPE_SYSTEM_ERROR: case TYPE_SYSTEM_OVERLAY: permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW; break; default: permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; } if (permission != null) { if (permission == android.Manifest.permission.SYSTEM_ALERT_WINDOW) { final int callingUid = Binder.getCallingUid(); // system processes will be automatically allowed privilege to draw if (callingUid == Process.SYSTEM_UID) { return WindowManagerGlobal.ADD_OKAY; } // check if user has enabled this operation. SecurityException will be thrown if // this app has not been allowed by the user final int mode = mAppOpsManager.checkOp(outAppOp[0], callingUid, attrs.packageName); switch  { case AppOpsManager.MODE_ALLOWED: case AppOpsManager.MODE_IGNORED: // although we return ADD_OKAY for MODE_IGNORED, the added window will // actually be hidden in WindowManagerService return WindowManagerGlobal.ADD_OKAY; case AppOpsManager.MODE_ERRORED: return WindowManagerGlobal.ADD_PERMISSION_DENIED; default: // in the default mode, we will make a decision here based on // checkCallingPermission() if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) { return WindowManagerGlobal.ADD_PERMISSION_DENIED; } else { return WindowManagerGlobal.ADD_OKAY; } } } if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { return WindowManagerGlobal.ADD_PERMISSION_DENIED; } } return WindowManagerGlobal.ADD_OKAY;}

从源码中得以看看,其实 TYPE_TOAST 未有做权限检查,直接重临了 WindowManagerGlobal.ADD_OKAY,所以啊,那便是怎能够绕过权力的来头。还也会有须求在乎的一点是 addView 方法中会调用到 mPolicy.adjustWindowParamsLw(win.mAttrs);,这些方法在分歧的本子有分化的兑现:

//Android 2.0 - 2.3.7 PhoneWindowManagerpublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_SYSTEM_OVERLAY: case TYPE_SECURE_SYSTEM_OVERLAY: case TYPE_TOAST: // These types of windows can't receive input events. attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; break; }}//Android 4.0.1 - 4.3.1 PhoneWindowManagerpublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_SYSTEM_OVERLAY: case TYPE_SECURE_SYSTEM_OVERLAY: case TYPE_TOAST: // These types of windows can't receive input events. attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; break; }}//Android 4.4 PhoneWindowManager@Overridepublic void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { switch (attrs.type) { case TYPE_SYSTEM_OVERLAY: case TYPE_SECURE_SYSTEM_OVERLAY: // These types of windows can't receive input events. attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; break; }}

能够见见,在4.0.1在此以前, 当我们应用 TYPE_TOAST, Android 会偷偷给我们增加 FLAG_NOT_FOCUSABLE 和 FLAG_NOT_TOUCHABLE,4.0.1 起首,会额外再去掉FLAG_WATCH_OUTSIDE_TOUCH,那样真的是怎么样风云都没了。而 4.4 最初,TYPE_TOAST 被移除了, 所以从 4.4 最早,使用 TYPE_TOAST 的同不时间还能接到触摸事件和开关事件了,而4.4在先只可以展现出来,不能够互相,所以 API18 及以下使用 TYPE_TOAST 是回天无力收到触摸事件的,可是幸运的是除了 miui 之外,那一个本子可以一直在 Manifest 文件中申明 android.permission.SYSTEM_ALERT_WINDOW权力,然后径直运用 WindowManager.LayoutParams.TYPE_PHONE 或者 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 都以足以一直弹出悬浮窗的。 还会有二个需求提到的是 TYPE_APPLICATION,那一个type 是极其 Activity 在当前 应用软件 内部使用的,也正是说,回到 Launcher 分界面,这么些悬浮窗是会未有的。 即使这种措施确实可以绕过权力,至于适配的坑呢,有人碰着之后方可联系本身,作者会持续到家。可是是因为那样能够不报名权限就弹出悬浮窗,并且在新型的 6.0+ 系统上也远非修复,所以就算这一个漏洞被滥用,就能够促成黄金时代部分想不到的结局,由此笔者个人倾向于接收QQ 的适配方案,也正是下面的符合规律适配流程去管理那个权力。

认清手提式有线电电话机rom

检查评定选拔是不是有权力,可避防备十分,可能点击事件没反应。为了给客商提供越来越好地体验,大家相应引导客户去权限设置页面展开权限。那个非常rom的权位设置是不蓬蓬勃勃致的,所以须要先判定手机rom,再分别去相应的权杖设置页面。

具体方法见:Android判别手提式有线电话机ROM

WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

更新:7.1.1后头版本

摩登发今后 7.1.1 版本之后选用 type_toast 重复增添四遍悬浮窗,第壹遍会崩溃,跑出来下边包车型客车失实:

E/AndroidRuntime: FATAL EXCEPTION: main android.view.WindowManager$BadTokenException: Unable to add window -- window android.view.ViewRootImpl$W@d7a4e96 has already been added at android.view.ViewRootImpl.setView(ViewRootImpl.java:691) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93) at com.tencent.ysdk.module.icon.impl.a.g(Unknown Source) at com.tencent.ysdk.module.icon.impl.floatingviews.q.onAnimationEnd(Unknown Source) at android.view.animation.Animation$3.run(Animation.java:381) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6119) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

去追溯源码,发掘是这里抛出来的谬误:

try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);} catch (RemoteException e) { .....} finally { if  { attrs.restore(); }}.....if (res < WindowManagerGlobal.ADD_OKAY) { ..... switch  { .... case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); }}

下一场去查看抛出这些丰裕处的代码:

if (mWindowMap.containsKey(client.asBinder { Slog.w(TAG_WM, "Window " + client + " is already added"); return WindowManagerGlobal.ADD_DUPLICATE_ADD;}

然后大家从 mWindowMap 以此变量出发去解析,不过最终发现,根本不行,那些代码从 5.X 版本就存在了,何况每一趟调用 addview 方法去增多一个 view 的时候,都是一个新的 client 对象,所以 mWindowMap.containsKey(client.asBinder 平昔是不创造的,所以无法从此以往处去解析,于是三番若干回剖判在 7.0 版本是未有毛病的,可是在 7.1.1 版本就应运而生难题了,所以我们去查看 7.1.1 版本代码的变动: type_toast 的连带改动:

图片 3这里写图片描述最终一定到了 aa07653 那多少个提交,我们看看此次提交改进的开始和结果:图片 4这里写图片描述然后点开 WMS 的纠正:图片 5此间写图片描述去到 canAddToastWindowForUid:图片 6那边写图片描述大家于是定位到了首要7.1.1 上边不能够再次增添 type_toast 类型 window 的来头!! 其余还也是有一点亟待注意的是,在 7.1.1 上边还扩大了之类的代码: 图片 7这里写图片描述 图片 8此间写图片描述可以看看在 25 版本之后,注意是未来,也等于 8.0,系统将会限定 type_toast 的施用,会直接抛出万分,这也是内需在意的地点。

特别谢谢ruanqin0706同学的用力支援,通过优测网的机型的测验适配,以往计算结果如下所示:

履新,6.0三星的适配方案不能够选用google API,照旧要运用 6.0 在此之前的适配方法,已经适配完毕~ 6.0 上两头的机型都以足以的,除了One plus这种奇葩机型:

机型 版本 详细信息 适配完成 具体表现
魅族 PRO6 6.0 型号:PRO6;版本:6.0;分辨率:1920*1080 检测权限结果有误,微信可正常缩小放大,而我方检测为未开启权限,为跳转至开启权限页
魅族 U20 6.0 型号:U20;版本:6.0;分辨率:1920*1080 检测权限结果有误,微信可正常缩小放大,而我方检测为未开启权限,为跳转至开启权限页

结论:

汇总结果
Android6.0 及以上机型覆盖:58款,其中:
三星:10款,均正常
华为:21款,均正常
小米:5款,均正常
魅族:2款,异常(1.检测权限未开启,点击 Android 6.0 及以上跳转,无法跳转,却可以选择魅族手机设置,设置后,悬浮窗打开缩小正常;2.在魅族上,及时设置悬浮窗关闭,微信也可正常缩小,但是我们检测的悬浮窗是否开发结果,和实际系统的设置是匹配的。)
其他:20款,均正常

已适配完毕,针对小米的手提式有线电话机,在 6.0 之后依旧选取老的跳转格局,并非使用新本子的 谷歌 API 进行跳转。</br>

这边是国产手提式有线电话机的测量检验结果:

机型 版本 适配完成 具体表现 默认设置
华为荣耀x2 5.0 跳转至通知中心页面,而非悬浮窗管理处 默认关闭
华为畅玩4x 4.4.4 可以优化 跳转至通知中心标签页面,用户需切换标签页(通知中心、悬浮窗为两个不同标签页) 默认关闭
华为 p8 lite 4.4.4 可以优化 跳转至通知中心标签页面,用户需切换标签页(通知中心、悬浮窗为两个不同标签页) 默认关闭
华为荣耀 6 移动版 4.4.2 可以优化 跳转至通知中心标签页面,用户需切换标签页(通知中心、悬浮窗为两个不同标签页) 默认关闭
华为荣耀 3c 电信版 4.3 跳转至通知中心,但默认是开启悬浮窗的 默认关闭
华为 G520 4.1.2 直接点击华为跳转设置页按钮,闪退 默认开启

结论:

汇总结果 完全兼容机型数量 次兼容机型数量 总测试机型数 兼容成功率
华为6.0以下机型覆盖:18款,其中:5.0.1以上:11款,均默认开启,且跳转设置页面正确;5.0:1款,处理异常(默认未开启悬浮窗权限,且点击跳转至通知栏,非悬浮窗设置入口)4.4.4、4.4.2:3款,处理可接受(默认未开启悬浮窗权限,点击跳转至通知中心的“通知栏”标签页,可手动切换至“悬浮窗”标签页设置)4.3:1款,处理可接受(默认开启,但点击华为跳转设置页,跳转至通知中心,无悬浮窗设置处)4.2.2:1款,默认开启,处理正常4.1.2:1款,处理有瑕疵(默认开启,但若直接点击华为跳转按钮,出现闪退) 12 5 18 94.44%

正在适配中...</br>

一大半的金立机型都以可以成功适配,除了有些奇异的机型:

机型 版本 适配完成 具体表现
小米 MI 4S 5.1.1 无悬浮窗权限,点击小米手机授权页跳转按钮,无反应
小米 红米NOTE 1S 4.4.4 未执行 未修改开启悬浮窗成功,真机平台不支持(为权限与之前系统有别)
小米 红米1 4.2.2 未执行 未安装成功

</br>结论:

汇总结果 完全兼容机型数量 次兼容机型数量 总测试机型数 兼容成功率
小米6.0以下机型覆盖:10款,其中:5.1.1 小米 MI 4S:1款,兼容失败(默认未开启,点击小米手机授权按钮,无跳转)其他:9款,均成功 9 0 10 90%

大约 百分百 的机型都以配完美,结论:

汇总结果 完全兼容机型数量 次兼容机型数量 总测试机型数 兼容成功率
三星6.0以下机型覆盖:28款,全部检测处理成功(默认均开启悬浮窗权限) 28 0 28 100%

大青大厂的机械,只测量检验了七款机型,都是OK的:

机型 版本 适配完成 是否默认开启
OPPO R7sm 5.1.1 默认开启
OPPO R7 Plus 5.0 默认开启
OPPO R7 Plus 5.1.1 默认开启
OPPO A37m 5.1 未执行 默认未开启,且无法设置开启(平台真机限制修改权限导致)
OPPO A59m 5.1.1 默认开启

结论:</br>

汇总结果
抽查3款,2个系统版本,均兼容,100%

</br>

其余的机型,HUAWEI 和 Sony 大法之类的机械,随机抽取了三款,也都以 OK 的:

机型 是否正常
蓝魔 R3
HTC A9
摩托罗拉 Nexus 6
VIVO V3Max A
金立 M5
HTC One E8
努比亚 Z11 Max
Sony Xperia Z3+ Dual
酷派 大神Note3
三星 GALAXY J3 Pro
三星 Note 5
中兴 威武3
中兴 Axon Mini

结论</br>

汇总结果
随机抽查看13款,全部测试正常,100%

开辟有些rom的权杖设置页面

未完待续。。。

参考:

  1. https://github.com/czy1121/settingscompat
  2. Android 悬浮窗权限各机型各系统适配大全

http://blog.csdn.net/adrianandroid/article/details/49911681

http://blog.csdn.net/u012573920/article/details/49514115

http://blog.csdn.net/adrianandroid/article/details/49911681

http://www.cnblogs.com/fangyucun/p/4027750.html

http://blog.csdn.net/xx326664162/article/details/52438706

编辑:互联网科技 本文来源:One plus TYPE_TOAST 悬浮窗无效的缘由

关键词: