2017-09-12 3 views
8

J'ai l'application MDM pour les parents de contrôler les appareils de l'enfant et il utilise la permission SYSTEM_ALERT_WINDOW pour afficher des avertissements sur l'appareil de l'enfant lorsque l'action interdite effectuée. Sur les appareils M + lors de l'installation de l'application vérifie l'autorisation d'utiliser cette méthode:Pourquoi dans la méthode Android O Settings.canDrawOverlays() renvoie "false" lorsque l'utilisateur a accordé l'autorisation de dessiner des superpositions et retourne à mon application?

Settings.canDrawOverlays(getApplicationContext()) 

et si ce retour de la méthode false l'application ouvre le dialogue du système où l'utilisateur peut accorder l'autorisation:

Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 
        Uri.parse("package:" + getPackageName())); 
startActivityForResult(intent, REQUEST_CODE); 

Dans Android O, lorsque l'utilisateur accorde avec succès l'autorisation et revient à l'application en appuyant sur le bouton précédent, la méthode canDrawOverlays() toujours retourner false, jusqu'à ce que l'utilisateur ne ferme pas l'application et l'ouvrir à nouveau ou simplement le choisir dans les récentes applications. Je l'ai testé sur la dernière version de l'appareil virtuel avec Android O dans Android Studio parce que je n'ai pas de véritable appareil.

J'ai fait un peu de recherche et, en outre vérifier l'autorisation avec AppOpsManager:

AppOpsManager appOpsMgr = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); 
int mode = appOpsMgr.checkOpNoThrow("android:system_alert_window", android.os.Process.myUid(), getPackageName()); 
Log.d(TAG, "android:system_alert_window: mode=" + mode); 

Et:

  • lorsque l'application ne dispose pas de cette autorisation, le mode est "2" (MODE_ERRORED) (canDrawOverlays() renvoie false) lorsque l'utilisateur
  • a accordé la permission et est revenu à l'application, le mode est "1" (MO DE_IGNORED) (canDrawOverlays() retours false)
  • et si vous ouvrez à nouveau maintenant l'application, le mode est "0" (MODE_ALLOWED) (canDrawOverlays() retours true)

S'il vous plaît, quelqu'un peut-il expliquer ce comportement à moi? Puis-je compter sur mode == 1 de l'opération "android:system_alert_window" et supposer que l'utilisateur a accordé la permission?

Répondre

2

J'ai également rencontré ce problème avec checkOp. Dans mon cas, j'ai le flux qui permet de rediriger vers les paramètres uniquement lorsque l'autorisation n'est pas définie. Et le AppOps est défini uniquement lors de la redirection vers les paramètres.

En supposant que le rappel AppOps est appelé uniquement lorsque quelque chose est modifié et qu'il n'y a qu'un seul commutateur, qui peut être modifié. Cela signifie que si l'appel est appelé, l'utilisateur doit accorder l'autorisation.

if (VERSION.SDK_INT >= VERSION_CODES.O && 
       (AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW.equals(op) && 
        packageName.equals(mContext.getPackageName()))) { 
    // proceed to back to your app 
} 

Après l'application est rétablie, la vérification avec canDrawOverlays() commence a fonctionné pour moi. Pour sûr, je redémarre l'application et vérifie si l'autorisation est accordée par voie standard.

Ce n'est certainement pas une solution parfaite, mais cela devrait fonctionner, jusqu'à ce que nous en sachions plus à ce sujet de la part de Google.

EDIT: Je demandé à Google: https://issuetracker.google.com/issues/66072795

EDIT 2: Google corrige cela. Mais il semble que la version Android O sera encore affectée.

+0

Ils ont réagi très rapidement, très bien. C'est la première fois que je rencontre un bug non corrigé dans Android. Je pense que la question est fermée et marquez votre poste comme une réponse. – fishbone

+0

Pouvez-vous s'il vous plaît partager le code complet pour vérifier si l'application est accordée avec cette autorisation? –

8

J'ai rencontré le même problème. J'utilise une solution de contournement qui tente d'ajouter une superposition invisible. Si une exception est levée, l'autorisation n'est pas accordée. Ce n'est peut-être pas la meilleure solution, mais cela fonctionne. Je ne peux rien vous dire sur la solution AppOps, mais elle semble fiable.

/** 
* Workaround for Android O 
*/ 
public static boolean canDrawOverlays(Context context) { 
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; 
    else if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { 
     return Settings.canDrawOverlays(context); 
    } else { 
     if (Settings.canDrawOverlays(context)) return true; 
     try { 
      WindowManager mgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 
      if (mgr == null) return false; //getSystemService might return null 
      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) { 
      e.printStackTrace(); 
     } 
     return false; 
    } 
} 
+0

Merci pour votre réponse. J'ai été surpris mais sur tous mes appareils de test: Xiaomi Redmi 4 (Android 6.0), Galaxy J3 (Android 7.0) et Galaxy S8 (Android 7.1), les superpositions fonctionne sans aucune vérification et demande de permission, seule déclaration dans le Manifeste. J'ai toujours utilisé ce morceau de code hérité comme axiome et je ne l'ai jamais testé. Je l'ai laissé tel quel, mais juste ajouté dans le code, où je vérifie la permission, une condition pour Android O, pour éviter la boucle, et TODO commenter pour faire attention à cet endroit à l'avenir. – fishbone

+0

Pour une raison quelconque, sur Android 8.1, cela renvoie true, même si dans l'écran des paramètres de draw-on-top, l'application ne bénéficie pas de cette autorisation. Comment est-ce possible? –

+0

Avec Android 8.1 et supérieur, cela renvoie 'Settings.canDrawOverlays' parce que le bug a été soi-disant corrigé par Google en Octobre. Comme Android 8.1 a été publié plus tard que cela devrait contenir le correctif. Si cela ne change pas juste> = à> et vous devriez être bon pour aller. – Ch4t4r

1

Dans mon cas, je ciblais niveau de l'API < Oreo et la méthode de superposition invisible dans la réponse de Ch4t4 ne fonctionne pas car il ne jette pas une exception.

Élaborant sur la réponse de l0v3 ci-dessus, et la nécessité de se prémunir contre l'utilisateur de basculer l'autorisation plus d'une fois, j'utilisé le code ci-dessous (vérification de la version Android omis):

Dans l'activité/fragment:

Context context; /* get the context */ 
boolean canDraw; 
private AppOpsManager.OnOpChangedListener onOpChangedListener = null;   

Pour demander l'autorisation de l'activité/fragment:

AppOpsManager opsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 
canDraw = Settings.canDrawOverlays(context); 
onOpChangedListener = new AppOpsManager.OnOpChangedListener() { 

    @Override 
    public void onOpChanged(String op, String packageName) { 
     PackageManager packageManager = context.getPackageManager(); 
     String myPackageName = context.getPackageName(); 
     if (myPackageName.equals(packageName) && 
      AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW.equals(op)) { 
      canDraw = !canDraw; 
     } 
    } 
}; 
opsManager.startWatchingMode(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, 
      null, onOpChangedListener); 
startActivityForResult(intent, 1 /* REQUEST CODE */); 

Et à l'intérieur onActivityResult

@Override 
public void onActivityResult(int requestCode, int resultCode, Intent data) { 
    if (requestCode == 1) { 
     if (onOpChangedListener != null) { 
      AppOpsManager opsManager = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 
      opsManager.stopWatchingMode(onOpChangedListener); 
      onOpChangedListener = null; 
     } 
     // The draw overlay permission status can be retrieved from canDraw 
     log.info("canDrawOverlay = {}", canDraw); 
    }  
} 

Pour se prémunir contre votre activité détruite en arrière-plan, à l'intérieur onCreate:

if (savedInstanceState != null) { 
    canDraw = Settings.canDrawOverlays(context); 
} 

et à l'intérieur onDestroy, stopWatchingMode devrait être invoqué si onOpChangedListener est non nul, semblable à onActivityResult ci-dessus.

Il est important de noter que, à compter de la mise en œuvre actuelle (Android O), le système ne dédupliquera pas les écouteurs enregistrés avant de rappeler. En enregistrant startWatchingMode(ops, packageName, listener), l'écouteur sera appelé pour des opérations de correspondance OU un nom de paquet correspondant, et au cas où les deux correspondraient, sera appelé 2 fois, donc le nom du paquet est défini sur null ci-dessus pour éviter l'appel en double. En outre, l'enregistrement de l'écouteur plusieurs fois, sans être désinscrit par stopWatchingMode, entraînera l'appel de l'écouteur plusieurs fois - ceci s'applique également à tous les cycles de vie Activity destroy-create.

Une alternative à ce qui précède est de définir un délai d'environ 1 seconde avant d'appeler Settings.canDrawOverlays(context), mais la valeur du délai dépend du périphérique et peut ne pas être fiable. (Référence: https://issuetracker.google.com/issues/62047810)

+0

Cela vous dit seulement que quelque chose a changé là, non? Qu'en est-il vraiment de savoir qu'il a été accordé? Pour moi, 'Settings.canDrawOverlays' renvoie toujours false. Testé sur Android 8.1, Pixel 2. –

+0

Le 'canDraw' booléen bascule quand il y a des changements. Puisque 'Settings.canDrawOverlays' fonctionne en dehors de' onActivityResult' (au moins dans l'émulateur), ceci est utilisé pour initialiser l'état du booléen. Cela fonctionne autour du bug sur Android 8.0. Vous ne savez pas s'il y a des bugs séparés dans Pixel 2/Android 8.1. – headuck

+0

ok. Merci. Je ne comprends pas pourquoi j'ai un problème sérieux sur mon Pixel 2. Rapporté à ce sujet ici: https://issuetracker.google.com/issues/72479203 –