Как обработать разрешение SYSTEM_ALERT_WINDOW, которое не предоставляется автоматически на некоторых устройствах перед зефиром

Я получаю сообщения о том, что некоторые устройства Xiaomi (например, Mi 2, с API-интерфейсом 21-го уровня) не показывают оверлеи. Мое приложение предназначено для API 23.

Есть несколько сообщений об этом. Кажется, что устройства MIUI не включают это разрешение во время установки (в отличие от других устройств, предшествующих Зефиру).

К несчастью, Settings.canDrawOverlays() работает только на Android 23+.

  1. Как правильно проверить, не было ли это разрешение еще до зефира?
  2. Есть ли намерение перевести пользователя на соответствующую страницу настроек MUIU? Может быть: new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", packageName) но у меня нет средств проверить это.

5 ответов

Решение

Проверка наличия разрешения drawOverlays безопаснее при использовании этого:

@SuppressLint("NewApi")
public static boolean canDrawOverlayViews(Context con){
    if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){return true;}
    try { 
        return Settings.canDrawOverlays(con); 
    }
    catch(NoSuchMethodError e){ 
        return canDrawOverlaysUsingReflection(con); 
    }
}


public static boolean canDrawOverlaysUsingReflection(Context context) {

    try {

        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        Class clazz = AppOpsManager.class;
        Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
        //AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
        int mode = (Integer) dispatchMethod.invoke(manager, new Object[] { 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });

        return AppOpsManager.MODE_ALLOWED == mode;

    } catch (Exception e) {  return false;  }

}

Пользовательские ПЗУ могли изменить ОС, так что Settings.canDrawOverlays() недоступен. Это случилось со мной с устройствами Xiaomi, и приложение упало.

Запрос разрешения:

@SuppressLint("InlinedApi")
public static void requestOverlayDrawPermission(Activity act, int requestCode){
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + act.getPackageName()));
    act.startActivityForResult(intent, requestCode);

}

1) в pre-API 23 разрешение уже дано, потому что пользователь предоставил его после установки.

РЕДАКТИРОВАТЬ: кажется, есть ошибка на Android 6 (которая будет исправлена ​​на 6.0.1), что, если пользователь отказал в этом разрешении, приложение завершится с SecurityException. Хотя понятия не имею, как Google это исправил.

2) Таким образом:

public static void requestSystemAlertPermission(Activity context, Fragment fragment, int requestCode) {
    if (VERSION.SDK_INT < VERSION_CODES.M)
        return;
    final String packageName = context == null ? fragment.getActivity().getPackageName() : context.getPackageName();
    final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName));
    if (fragment != null)
        fragment.startActivityForResult(intent, requestCode);
    else
        context.startActivityForResult(intent, requestCode);
}

Затем в onActivityResult вы можете проверить, дано ли разрешение или нет, как таковое:

@TargetApi(VERSION_CODES.M)
public static boolean isSystemAlertPermissionGranted(Context context) {
    final boolean result = VERSION.SDK_INT < VERSION_CODES.M || Settings.canDrawOverlays(context);
    return result;
}

РЕДАКТИРОВАТЬ: в настоящее время, если вы опубликуете приложение в Play Store, ваше приложение будет автоматически предоставлено с этим разрешением. Вы можете прочитать об этом здесь. Когда я спросил об этом, я подумал, что это часть самого Android, так как я думал, что все, что нам нужно, это настроить достаточно высокое значение targetSdkVersion. Google написал мне ( здесь), что они хотели избежать проблем с популярными приложениями.

Я предлагаю правильно обрабатывать это разрешение, даже если вы получите его автоматически.

Вот шаг за шагом, как справиться с этим:

Сначала дайте ниже разрешение в файле манифеста:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Или же

<uses-permission-sdk-23 android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Затем обработайте остальные вещи, используя следующий код:

 public final static int REQUEST_CODE = 65635;

    public void checkDrawOverlayPermission() {
        /** check if we already  have permission to draw over other apps */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                /** 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) {
                if (Settings.canDrawOverlays(this)) {
                    // continue here - permission was granted
                    goYourActivity();
                }
            }
        }
    }

Просто позвони checkDrawOverlayPermission() от вашего LauncherActivity или в любом другом месте, как ваши требования.

Когда вы выполните проект, вы увидите окно и попросите включить разрешение. После разрешения вы сможете делать с этим все что угодно.

Для поиска пока о проблеме в Xiaomi, Meizu я нашел это. Работает отлично..

public static boolean isMiuiFloatWindowOpAllowed(@NonNull Context context) {
    final int version = Build.VERSION.SDK_INT;

    if (version >= 19) {
        return checkOp(context, OP_SYSTEM_ALERT_WINDOW); //See AppOpsManager.OP_SYSTEM_ALERT_WINDOW=24 /*@hide/
    } else {
        return (context.getApplicationInfo().flags & 1<<27) == 1;
    }
}

public static boolean checkOp(Context context, int op, String packageName, int uid) {
    final int version = Build.VERSION.SDK_INT;

    if (version >= 19) {
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        try {
            return (AppOpsManager.MODE_ALLOWED == (Integer) ReflectUtils.invokeMethod(manager, "checkOp", op, uid, packageName));
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        Flog.e("Below API 19 cannot invoke!");
    }
    return false;
}

ReflectUtils.java

public static Object invokeMethod(@NonNull Object receiver, String methodName, Object... methodArgs) throws Exception {
Class<?>[] argsClass = null;
    if (methodArgs != null && methodArgs.length != 0) {
        int length = methodArgs.length;
        argsClass = new Class[length];
        for (int i=0; i<length; i++) {
            argsClass[i] = getBaseTypeClass(methodArgs[i].getClass());
        }
    }

    Method method = receiver.getClass().getMethod(methodName, argsClass);
    return method.invoke(receiver, methodArgs);
}

Ссылка

Вы можете проверить разрешение следующим образом:

boolean granted = activity.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW") == PackageManager.PERMISSION_GRANTED;
Другие вопросы по тегам