現在很多應用都會用到懸浮窗,很多國產rom把懸浮窗權限加入控制了,你就需要判斷是否有懸浮窗權限,然後做對應操作。
Android 原生有自帶權限管理的,只是被隱藏了。看android源碼在android.app下就有個AppOpsManager類。
類說明如下:
/** * API for interacting with "application operation" tracking. * * <p>This API is not generally intended for third party application developers; most * features are only available to system applications. Obtain an instance of it through * {@link Context#getSystemService(String) Context.getSystemService} with * {@link Context#APP_OPS_SERVICE Context.APP_OPS_SERVICE}.</p> */上面說明了只對系統應用有用,rom廠商們應該就是利用這個AppOps機制開放一些權限控制。
我們要判斷是否有權限該如何做呢?就只能通過反射去判斷了。
AppOpsManager的checkOp方法,就是檢測是否有某項權限的方法有這些返回值,分別是允許,忽略,錯誤和默認:
/** * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is * allowed to perform the given operation. */ public static final int MODE_ALLOWED = 0; /** * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is * not allowed to perform the given operation, and this attempt should * <em>silently fail</em> (it should not cause the app to crash). */ public static final int MODE_IGNORED = 1; /** * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the * given caller is not allowed to perform the given operation, and this attempt should * cause it to have a fatal error, typically a {@link SecurityException}. */ public static final int MODE_ERRORED = 2; /** * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should * use its default security check. This mode is not normally used; it should only be used * with appop permissions, and callers must explicitly check for it and deal with it. */ public static final int MODE_DEFAULT = 3;只有MODE_ALLOWED纔是確定有權限的。
類裏面checkOp方法如下,三個參數分別是操作id,uid和包名:
/** * 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; }操作id即op可以在該類中找到靜態值定義,android23裏面有62種權限,我們需要的是OP_SYSTEM_ALERT_WINDOW=24
知道這些就可以用反射把我們的方法寫出了:
- /**
- * 判斷 懸浮窗口權限是否打開
- *
- * @param context
- * @return true 允許 false禁止
- */
- public static boolean getAppOps(Context context) {
- try {
- Object object = context.getSystemService("appops");
- if (object == null) {
- return false;
- }
- Class localClass = object.getClass();
- Class[] arrayOfClass = new Class[3];
- arrayOfClass[0] = Integer.TYPE;
- arrayOfClass[1] = Integer.TYPE;
- arrayOfClass[2] = String.class;
- Method method = localClass.getMethod("checkOp", arrayOfClass);
- if (method == null) {
- return false;
- }
- Object[] arrayOfObject1 = new Object[3];
- arrayOfObject1[0] = Integer.valueOf(24);
- arrayOfObject1[1] = Integer.valueOf(Binder.getCallingUid());
- arrayOfObject1[2] = context.getPackageName();
- int m = ((Integer) method.invoke(object, arrayOfObject1)).intValue();
- return m == AppOpsManager.MODE_ALLOWED;
- } catch (Exception ex) {
- }
- return false;
- }
測試在魅族華爲小米大部分機型上都是可以的,但這個方法也不能保證正確,一些機型上會返回錯誤即MODE_ERRORED,就是獲取不到權限值,這個方法就返回了false,但實際上懸浮窗是可以使用的。
第二篇針對MIUI:
- /**
- * Created by chenzy on 2015/3/31.
- *
- * MIUI 懸浮窗判斷工具類
- */
- public class AlterWindowUtil {
- public static final String TAG ="AlterWindowUtil";
- /**
- * 4.4 以上可以直接判斷準確
- *
- * 4.4 以下非MIUI直接返回true
- *
- * 4.4 以下MIUI 可 判斷 上一次打開app 時 是否開啓了懸浮窗權限
- *
- * @param context
- * @return
- */
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static boolean isFloatWindowOpAllowed(Context context) {
- final int version = Build.VERSION.SDK_INT;
- if(!DeviceUtil.isFlyme4() && !DeviceUtil.isMiui(context)){
- return true;
- }
- if (version >= 19) {
- return checkOp(context, 24); //自己寫就是24 爲什麼是24?看AppOpsManager //AppOpsManager.OP_SYSTEM_ALERT_WINDOW
- } else {
- if(DeviceUtil.isMiui(context)){
- if ((context.getApplicationInfo().flags & 1 << 27) == 1 <<27 ) {
- return true;
- } else {
- return false;
- }
- }else{
- return true;
- }
- }
- }
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public 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 managerClass = manager.getClass();
- Method method = managerClass.getDeclaredMethod("checkOp", int.class, int.class, String.class);
- int isAllowNum = (Integer) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
- if (AppOpsManager.MODE_ALLOWED == isAllowNum) {
- return true;
- } else {
- return false;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return false;
- }
- }