懸浮窗許可權
許可權
1、註冊許可權
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2、動態申請許可權
API19以後需要動態申請許可權,API23以前預設是開放的,但是個別廠商自己定製了開啟許可權驗證,比較坑爹,API23以後google提供的統一了跳轉頁面.
騷操作(建議還是官方提供的操作來)
19<=api<26 可以使用type = TYPE_TOAST 官方產生的漏洞,不需要靜態註冊也不需要動態申請許可權.參考https://blog.csdn.net/u013651405/article/details/79350857 測試結果,小米和vivo不在其中.
檢查機型
<pre>import android.os.Build; import android.text.TextUtils; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 22:03 */ public class RomUtils { private static final String TAG = "RomUtils"; /** * 獲取 emui 版本號 * @return */ public static double getEmuiVersion() { try { String emuiVersion = getSystemProperty("ro.build.version.emui"); String version = emuiVersion.substring(emuiVersion.indexOf("_") + 1); return Double.parseDouble(version); } 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(1)); } 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()), 1024); 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("HUAWEI"); } /** * 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("Meizu"); String meizuFlymeOSFlag= getSystemProperty("ro.build.display.id"); if (TextUtils.isEmpty(meizuFlymeOSFlag)){ return false; }else if (meizuFlymeOSFlag.contains("flyme") || meizuFlymeOSFlag.toLowerCase().contains("flyme")){ returntrue; }else { return false; } } public static boolean checkIs360Rom() { //fix issue https://github.com/zhaozepeng/FloatWindowPermission/issues/9 return Build.MANUFACTURER.contains("QiKU") || Build.MANUFACTURER.contains("360"); } public static boolean checkIsOppoRom() { //https://github.com/zhaozepeng/FloatWindowPermission/pull/26 return Build.MANUFACTURER.contains("OPPO") || Build.MANUFACTURER.c</pre>
6.0之後
Google統一了跳轉入口
<pre>package com.blibee.im.base.util.float_window; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 22:22 */ public class CommonFloatStrategy implements IFloatStrategy { @Override public boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 23) { return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24; } return true; } @Override public void applyPermission(Activity context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { context.startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())), 0); } } @Override public boolean checkOp(Context context, int op) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return Settings.canDrawOverlays(context); } return true; }</pre>
4.4 - 6.0 之前
作死的廠商自己定製了申請入口,害苦了個大開發者門.以下提供大部分定製過申請頁面的機型.以及其申請程式碼.
xiaomi
<pre>import android.app.Activity; import android.app.AppOpsManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.provider.Settings; import android.util.Log; import java.lang.reflect.Method; import static com.blibee.im.base.util.float_window.RomUtils.getMiuiVersion; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 21:59 */ public class XiaoMiStrategy implements IFloatStrategy { private static final String TAG = "XiaoMiStrategy"; @Override public boolean checkFloatWindowPermission(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return checkOp(context, 24); } else { return true; } } @Override public void applyPermission(Activity context) { int versionCode = getMiuiVersion(); if (versionCode == 5) { goToMiuiPermissionActivity_V5(context); } else if (versionCode == 6) { goToMiuiPermissionActivity_V6(context); } else if (versionCode == 7) { goToMiuiPermissionActivity_V7(context); } else if (versionCode == 8) { goToMiuiPermissionActivity_V8(context); } else { Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode); } } @Override public 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 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false; } 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(Activity 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(uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (isIntentAvailable(intent, context)) { context.startActivityForResult(intent, 0); } else { Log.e(TAG, "intent is not available!"); } } /** * 小米 V6 版本 ROM許可權申請 */ public static void goToMiuiPermissionActivity_V6(Activity 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.startActivityForResult(intent, 0); } else { Log.e(TAG, "Intent is not available!"); } } /** * 小米 V7 版本 ROM許可權申請 */ public static void goToMiuiPermissionActivity_V7(Activity 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.startActivityForResult(intent, 0); } else { Log.e(TAG, "Intent is not available!"); } } /** * 小米 V8 版本 ROM許可權申請 */ public static void goToMiuiPermissionActivity_V8(Activity 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.startActivityForResult(intent, 0); } 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.startActivityForResult(intent, 0); } else { Log.e(TAG, "Intent is not available!"); </pre>
360
<pre>import android.app.Activity; import android.app.AppOpsManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build; import android.util.Log; import java.lang.reflect.Method; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 22:03 */ public class QikuFloatStrategy implements IFloatStrategy { private static final String TAG = "QikuFloatStrategy"; @Override public 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; } @Override public void applyPermission(Activity context) { Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (isIntentAvailable(intent, context)) { context.startActivityForResult(intent, 0); } else { intent.setClassName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); if (isIntentAvailable(intent, context)) { context.startActivityForResult(intent, 0); } else { Log.e(TAG, "can't open permission page with particular name, please use " + "\"adb shell dumpsys activity\" command and tell me the name of the float window permission page"); } } } @Override public 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 == (int)method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } else { Log.e("", "Below API 19 cannot invoke!"); } return false; } private static boolean isIntentAvailable(Intent intent, Context context) { if (intent == null) { return false; } return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; </pre>
oppo
<pre>import android.app.Activity; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Build; import android.util.Log; import java.lang.reflect.Method; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 22:02 */ public class OppoFloatStrategy implements IFloatStrategy { private static final String TAG = "OppoFloatStrategy"; @Override public 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; } @Override public void applyPermission(Activity context) { try { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //com.coloros.safecenter/.sysfloatwindow.FloatWindowListActivity ComponentName comp = new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity");//懸浮窗管理頁面 intent.setComponent(comp); context.startActivityForResult(intent, 0); } catch(Exception e){ e.printStackTrace(); } } @Override public 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 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false; }</pre>
meizu
作死的魅族6.0手機有些也是定製的頁面
<pre>import android.app.Activity; import android.app.AppOpsManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.provider.Settings; import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 21:53 */ public class MeiZuFloatStrategy implements IFloatStrategy { private static final String TAG = "MeiZuFloatStrategy"; @Override public 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; } @Override public void applyPermission(Activity context) { try { Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); intent.putExtra("packageName", context.getPackageName()); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivityForResult(intent, 0); } catch (Exception e) { try { Log.e(TAG, "獲取懸浮窗許可權, 開啟AppSecActivity失敗, " + Log.getStackTraceString(e)); commonROMPermissionApplyInternal(context); } catch (Exception eFinal) { Log.e(TAG, "獲取懸浮窗許可權失敗, 通用獲取方法失敗, " + Log.getStackTraceString(eFinal)); } } } public void commonROMPermissionApplyInternal(Activity context) throws NoSuchFieldException, IllegalAccessException { Class clazz = Settings.class; Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION"); Intent intent = new Intent(field.get(null).toString()); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse("package:" + context.getPackageName())); context.startActivityForResult(intent, 0); } @Override public 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 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false</pre>
huawei
<pre>import android.app.Activity; import android.app.AppOpsManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Build; import android.util.Log; import android.widget.Toast; import java.lang.reflect.Method; /** * @Auther: weiwei.zhang06 * @Date: 2019/3/25 21:51 */ public class HuaWeiFloatStrategy implements IFloatStrategy { private static final String TAG = "HuaWeiFloatStrategy"; @Override public 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; } @Override public void applyPermission(Activity context) { try { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//懸浮窗管理頁面 intent.setComponent(comp); if (RomUtils.getEmuiVersion() == 3.1) { //emui 3.1 的適配 context.startActivityForResult(intent, 0); } else { //emui 3.0 的適配 comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//懸浮窗管理頁面 intent.setComponent(comp); context.startActivityForResult(intent, 0); } } 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");//華為許可權管理,跳轉到本app的許可權管理頁面,這個需要華為介面許可權,未解決 intent.setComponent(comp); context.startActivityForResult(intent, 0); Log.e(TAG, Log.getStackTraceString(e)); } 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 intent.setComponent(comp); context.startActivityForResult(intent, 0); e.printStackTrace(); Log.e(TAG, Log.getStackTraceString(e)); } catch (Exception e) { //丟擲異常時提示資訊 Toast.makeText(context, "進入設定頁面失敗,請手動設定", Toast.LENGTH_LONG).show(); Log.e(TAG, Log.getStackTraceString(e)); } } @Override public 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 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { Log.e(TAG, Log.getStackTraceString(e)); } } else { Log.e(TAG, "Below API 19 cannot invoke!"); } return false; }</pre>
問題
permission denied for window type 2003
除了要註冊許可權和申請許可權外,可能會遇到type不支援的情況,建議對8.0以上的系統使用如下型別.
<pre>if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; }</pre>