2017-09-12 15 views
8

Mam aplikację MDM dla rodziców do kontrolowania urządzeń dziecięcych i używa ona pozwolenia SYSTEM_ALERT_WINDOW do wyświetlania ostrzeżeń na urządzeniu dziecka, gdy wykonano zabronioną akcję. na urządzeniach M + Podczas instalacji aplikacja sprawdza uprawnienia przy użyciu tej metody:Dlaczego w Androidzie metoda O Ustawienia .canDrawOverlays() zwraca "false", gdy użytkownik udzielił uprawnień do rysowania nakładek i wraca do mojej aplikacji?

Settings.canDrawOverlays(getApplicationContext()) 

i jeśli ta metoda powrotu false aplikacja otwiera okno dialogowe systemu, gdzie użytkownik może przyznać uprawnienie:

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

W Androida O, gdy użytkownik pomyślnie udzieli pozwolenia i powróci do aplikacji, naciskając przycisk Wstecz, metoda canDrawOverlays() nadal będzie zwracać false, dopóki użytkownik nie zamknie aplikacji i nie otworzy jej ponownie lub po prostu wybierze ją w oknie dialogowym ostatnich aplikacji. Przetestowałem go na najnowszej wersji urządzenia wirtualnego z Android O w Android Studio, ponieważ nie mam prawdziwego urządzenia.

Zrobiłem trochę badań i dodatkowo sprawdzić uprawnienia z 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); 

i tak:

  • , gdy aplikacja nie posiada tego uprawnienia, tryb to "2" (MODE_ERRORED) (canDrawOverlays() zwraca false), gdy użytkownik uzyska zezwolenie i wrócił do aplikacji, tryb wynosi "1" (MO DE_IGNORED) (canDrawOverlays() powraca false)
  • a jeśli teraz ponownie otworzyć aplikację, ten tryb jest "0" (MODE_ALLOWED) (canDrawOverlays() powraca true)

Proszę, może ktoś wyjaśnić ten problem dla mnie? Czy mogę polegać na mode == 1 operacji "android:system_alert_window" i założyć, że użytkownik udzielił pozwolenia?

Odpowiedz

2

Znalazłem ten problem z checkOp też. W moim przypadku mam przepływ, który pozwala przekierować do ustawień tylko wtedy, gdy uprawnienia nie są ustawione. A AppOps jest ustawiony tylko podczas przekierowywania do ustawień.

Przyjmując, że wywołanie zwrotne AppOps jest wywoływane tylko wtedy, gdy coś się zmieni i jest tylko jeden przełącznik, który można zmienić. Oznacza to, że jeśli wywoływany jest wywołanie zwrotne, użytkownik musi udzielić pozwolenia.

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

Po przywróceniu aplikacji, sprawdzanie z canDrawOverlays() startów pracował dla mnie. Na pewno ponownie uruchomię aplikację i sprawdzę, czy zezwolenie jest udzielane w standardowy sposób.

To zdecydowanie nie jest doskonałe rozwiązanie, ale powinno działać, dopóki nie dowiemy się więcej na ten temat od Google.

EDIT: Poprosiłem Google: https://issuetracker.google.com/issues/66072795

EDIT 2: Google naprawia to. Wygląda jednak na to, że wersja Androida O będzie nadal zagrożona.

+0

Zareagowali bardzo szybko, wspaniale. Po raz pierwszy spotykam nieusunięty błąd w systemie Android. Myślę, że problem został zamknięty i oznaczyć swój wpis jako odpowiedź. – fishbone

+0

Czy możesz podzielić się pełnym kodem, aby sprawdzić, czy aplikacja uzyskała to uprawnienie? –

8

Wpadłem na ten sam problem. Używam obejścia, które próbuje dodać niewidzialną nakładkę. Jeśli zostanie zgłoszony wyjątek, uprawnienia nie zostaną przyznane. To może nie być najlepsze rozwiązanie, ale działa. Nie mogę nic powiedzieć o rozwiązaniu AppOps, ale wygląda on na niezawodny.

/** 
* 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

Dzięki za odpowiedź. Byłem zaskoczony, ale na wszystkich moich urządzeniach testowych: Xiaomi Redmi 4 (Android 6.0), Galaxy J3 (Android 7.0) i Galaxy S8 (Android 7.1), nakładki działają bez żadnych kontroli i żądań uprawnień, tylko deklaracja w Manifest. Zawsze używałem tego fragmentu starszego kodu jako aksjomatu i nigdy go nie testuję. Zostawiłem go tak, jak jest, ale dodałem go w kodzie, gdzie sprawdzam pozwolenie, jeden warunek dla Androida O, aby uniknąć zapętlenia, i komentarz TODO, aby zwrócić uwagę na to miejsce w przyszłości. – fishbone

+0

Z jakiegoś powodu, na Androidzie 8.1, to zwraca wartość true, nawet jeśli na ekranie ustawień z losowaniem aplikacja nie ma takiego pozwolenia. Jak to mogło się stać? –

+0

Z Androidem 8.1 i nowszym zwraca 'Settings.canDrawOverlays', ponieważ błąd został naprawiony przez Google w październiku. Ponieważ Android 8.1 został wydany później, powinien zawierać poprawkę. Jeśli nie zmienia się po prostu> = na> i powinieneś być dobry. – Ch4t4r

1

W moim przypadku celowałem w poziom API < Oreo i niewidoczna metoda nakładki w odpowiedzi Ch4t4 nie działa, ponieważ nie rzuca wyjątku.

Opracowanie na odpowiedź l0v3 za powyższym, oraz konieczność ochrony przed użytkownikowi przełączanie uprawnienie więcej niż jeden raz, kiedyś poniższy kod (sprawdza wersja Androida pominięta):

w działalności/fragmentu:

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

Aby otrzymać pozwolenie na działalność/fragmentu:

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 */); 

i wewnątrz 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); 
    }  
} 

Aby ustrzec się przed swoją aktywność niszczone w tle, wewnątrz onCreate:

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

i wewnątrz onDestroy, stopWatchingMode powinna być wywołana jeśli onOpChangedListener nie jest null, podobny do onActivityResult powyżej.

Należy pamiętać, że od czasu obecnej implementacji (Android O) system nie usunie duplikatów zarejestrowanych słuchaczy przed oddzwonieniem. Rejestracja startWatchingMode(ops, packageName, listener) spowoduje, że detektor zostanie wezwany do wykonania pasujących operacji LUB dopasowania nazwy pakietu, aw przypadku, gdy oba pasują, zostaną wywołane 2 razy, więc nazwa pakietu jest ustawiona na null powyżej, aby uniknąć duplikowania połączenia. Również rejestracja słuchacza wiele razy, bez wyrejestrowania przez stopWatchingMode, spowoduje, że detektor zostanie wywołany wiele razy - dotyczy to również działań ratunkowych Activity-create.

Alternatywą dla powyższego jest ustawienie opóźnienia około 1 sekundy przed wywołaniem Settings.canDrawOverlays(context), ale wartość opóźnienia zależy od urządzenia i może nie być wiarygodna. (Numer referencyjny: https://issuetracker.google.com/issues/62047810)

+0

To tylko mówi ci, że coś się tam zmieniło, nie? A może naprawdę wiedząc, że to się spełniło? Dla mnie 'Settings.canDrawOverlays' zawsze zwraca wartość false. Testowane w systemie Android 8.1, Pixel 2. –

+0

Boolean 'canDraw' przełącza się, gdy są zmiany. Ponieważ 'Settings.canDrawOverlays' działa poza' onActivityResult' (przynajmniej w emulatorze), służy to do zainicjalizowania stanu boolowskiego. Działa to w przypadku błędu w systemie Android 8.0. Nie wiem, czy istnieją oddzielne błędy w Pixel 2/Android 8.1. – headuck

+0

ok. dzięki. Nie rozumiem, dlaczego mam poważny problem w moim Pixelu 2. Zgłoszono o tym tutaj: https://issuetracker.google.com/issues/72479203 –

Powiązane problemy