¿Pueden solicitar permisos sincrónicamente en el modelo de permisos de ejecución de Android Marshmallow (API 23)?

Digamos que tienes un método como este:

public boolean saveFile (Url url, String content) { // save the file, this can be done a lot of different ways, but // basically the point is... return save_was_successful; } 

A lo largo de tu aplicación, si quieres guardar un archivo en un almacenamiento externo, …

 if (saveFile(external_storage_url, "this is a test")) { // yay, success! } else { // notify the user something was wrong or handle the error } 

Este es un ejemplo simplificado, por lo que no obtenga mi caso sobre el bloqueo de la interfaz de usuario, el manejo de las excepciones correctamente, etc Si no te gusta el archivo de ahorro, se podría imaginar un getContact() o getPhoneState() o lo que sea. El punto es que es una operación que necesita permiso que devuelve un cierto valor (s) y se utiliza en toda la aplicación.

En Android <= Lollipop, si el usuario había instalado y accedió a conceder android.permission.WRITE_EXTERNAL_STORAGE o lo que sea, todo sería bueno.

Pero en el nuevo modelo de permiso de ejecución Marshmallow (API 23), antes de guardar el archivo en un almacenamiento externo, se supone que (1) comprueba si se ha concedido el permiso. Si no es así , posiblemente (2) muestre una justificación para la solicitud (si el sistema piensa que es una buena idea ) con un brindis o lo que sea y (3) pídale al usuario que conceda permiso a través de un diálogo, luego, llamar de vuelta…

(Para que su aplicación se sienta alrededor, esperando …)

(4) Cuando el usuario finalmente responde al diálogo, el método onRequestPermissionsResult () se desencadena, y su código ahora (5) tiene que tamizar a través de qué solicitud de permiso están respondiendo , si el usuario dijo que sí o no Conocimiento no hay manera de manejar "no" frente a "no y no preguntar de nuevo"), (6) averiguar lo que estaban tratando de lograr en primer lugar que provocó todo el proceso de solicitud de permisos, por lo que El programa puede finalmente (7) seguir adelante y hacer esa cosa.

Para saber lo que el usuario estaba tratando de hacer en el paso (6) implica haber pasado previamente un código especial (la "respuesta de solicitud de permisos") que se describe en los documentos como un identificador del tipo de solicitud de permiso (cámara / contacto / etc .) Pero me parece más como un "específicamente, lo que estaba tratando de hacer cuando se dio cuenta de que tendría que pedir permisos" código, dado que el mismo permiso / grupo puede ser utilizado para múltiples propósitos en su código, por lo que Necesitará utilizar este código para devolver la ejecución al lugar apropiado después de obtener el permiso.

Podría ser totalmente malentendido cómo se supone que esto funcione – así que por favor avíseme si estoy lejos – pero el punto más grande es que realmente no estoy seguro de cómo pensar en hacer todo lo anterior w / the saveFile() descrito anteriormente debido a la asincrónica "esperar a que el usuario responda" parte. Las ideas que he considerado son bastante hacky y ciertamente mal.

El podcast para desarrolladores de Android de hoy sugirió que puede haber una solución síncrona a la vuelta de la esquina, e incluso se habló de un tipo mágico de un paso para introducir una solicitud de permisos en Android Studio. Sin embargo, cómo el proceso de permiso de ejecución se puede empujar en un saveFile() o lo que sea … Estoy pensando en algo en la línea de:

 public boolean saveFile(Url url, String content) { // this next line will check for the permission, ask the user // for permission if required, maybe even handle the rationale // situation if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_storage_rationale)) { return false; // or throw an exception or whatever } else { // try to save the file return save_was_successful; } } 

Así que el checkPermission() fallaría si el usuario no tenía y también se negó a conceder el permiso. Tal vez uno podría usar un bucle alrededor de checkPermission() para intentar pedir hasta 3 veces o algo, o mejor sería si una sana no-ser-molesto-política fue manejado por el método.

¿Es posible tal cosa? ¿Deseable? ¿Alguna de estas soluciones bloquearía el hilo de la interfaz de usuario? Desde el podcast parecía que Google podría tener una solución como esta llegando a la vuelta de la esquina, pero quería tener pensamientos sobre si había algo – una clase de conveniencia, un patrón, algo – que no implica que todos tengan que Refactorizar todas las operaciones que requieren permiso, lo que debo asumir podría resultar bastante complicado.

Perdón por la larga pregunta, pero quería ser lo más completo posible. Tomaré mi respuesta del aire. ¡Gracias!


Actualización : Aquí está la transcripción del podcast mencionado anteriormente.

Escucha a eso de las 41:20 . En esta discusión:

Transcripción áspera:

Tor Norbye (equipo de herramientas): " Así que no parece que debería ser mucho trabajo para los desarrolladores, pero entiendo que parte del problema es que no son llamadas sincrónicas, ¿no? Cambiar la forma en que su actividad está escrita para tener una devolución de llamada– por lo que es realmente un poco como una máquina de estado donde … para este estado, usted- "

Poiesz (gerente de producto): " Ah- pensé que había una- podría haber una opción para la respuesta síncrona- "

Norbye: " Oh, eso haría las cosas … "

Poiesz: " Puedo hablar con la gente en el interior. Recuerdo una discusión sobre síncronos, pero podemos averiguarlo " .

Norbye: " Sí. De hecho, probablemente deberíamos convertirlo en las herramientas, donde tienes una fácil refactorización … "

Entonces él habla sobre el uso de anotaciones en las herramientas para determinar qué API requieren permisos .. (que a partir de ahora no funciona esa gran OMI) y cómo quiere que las herramientas algún día para generar realmente el código requerido si encuentra un "peligroso "Llamada al método:

Norbye: " … entonces si estás en M también dirás, 'hey, ¿estás realmente revisando este permiso o estás cogiendo excepciones de seguridad?', Y si no lo eres, diremos 'Probablemente necesite hacer algo por solicitar el permiso aquí.' Lo que me gustaría es que para que tenga una solución rápida donde se puede ir "CHING!" E inserta todas las cosas adecuadas para preguntar, pero la forma en que las cosas estaban de vuelta cuando miré, esto requirió la reestructuración de muchas cosas – la adición de interfaces y callbacks, y el cambio de flujo, y que no podíamos hacer. Un modo sincrónico fácil como una cosa temporal o permanente, eso sería [grande] " .

En cuanto a Marshmallow, mi comprensión es que no puedes.

Tuve que resolver el mismo problema en mi aplicación. He aquí cómo lo hice:

  1. Refactorización : Mueva cada fragmento de código que depende de permisos de algún tipo en un método propio.
  2. Más refactorización : Identifique el disparador de cada método (como iniciar una actividad, tocar un control, etc.) y agrupar los métodos si tienen el mismo desencadenador. (Si dos métodos terminan teniendo el mismo trigger Y requiriendo el mismo conjunto de permisos, considere la posibilidad de combinarlos.)
  3. Aún más refactoring : Averigüe lo que depende de los nuevos métodos que se han llamado antes, y asegúrese de que es flexible acerca de cuándo se llaman estos métodos: O bien moverlo en el método en sí, o al menos asegurarse de que lo que haces no Lanzar excepciones si su método no se ha llamado antes y que comienza a comportarse como se esperaba tan pronto como se llama al método.
  4. Códigos de solicitud : Para cada uno de estos métodos (o grupos de ellos), defina una constante entera que se utilizará como un código de solicitud.
  5. Comprobación y solicitud de permisos : Envuelva cada uno de estos métodos / grupos de métodos en el código siguiente:

.

 if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED) doStuffThatRequiresPermission(); else ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION); 
  1. Manejo de respuestas : Escriba una implementación para onRequestPermissionsResult() en cada Activity que solicite permisos. Compruebe si se concedieron los permisos solicitados y utilice el código de solicitud para determinar qué método debe llamarse.

Tenga en cuenta que la API requiere una Activity para las solicitudes de permisos de tiempo de ejecución. Si tiene componentes no interactivos (como un Service ), eche un vistazo a Cómo solicitar permisos de un servicio en Android Marshmallow para obtener consejos sobre cómo solucionarlo. Básicamente, la forma más fácil es mostrar una notificación que a continuación, mostrará una Activity que no hace nada, pero presentar el diálogo de permisos de tiempo de ejecución. Aquí es cómo abordé esto en mi aplicación.

Respuesta corta: no, no hay ninguna operación de sincronización hoy. Debe comprobar si tiene el permiso correcto antes de completar la operación o como última opción, podría poner un bloque try / catch para la excepción de seguridad. En el bloque catch puede informar al usuario que la operación ha fallado debido a problemas de permiso. Además, hay otro punto: cuando se revoca un permiso, la aplicación no se reinicia desde la actividad principal, por lo que debe comprobar el permiso incluso en su onResume ().

Así que odio responder a mi propia pregunta específicamente con respecto al permiso de android.permission.WRITE_EXTERNAL_STORAGE utilizado en mi ejemplo, pero qué diablos.

Para leer y / o escribir un archivo, en realidad hay una manera de evitar completamente tener que pedir y luego comprobar el permiso y así evitar el flujo completo que he descrito anteriormente. De esta manera, el saveFile (Url url, String content) que he dado como ejemplo podría seguir trabajando de forma síncrona.

La solución, que creo funciona en la API 19+, elimina la necesidad del permiso WRITE_EXTERNAL_STORAGE al tener un objeto DocumentsProvider como un "intermediario" para pedir al usuario en nombre de su aplicación "por favor seleccione el archivo específico para escribir" , Un "selector de archivos") y luego una vez que el usuario elige un archivo (o escribe un nuevo nombre de archivo), la aplicación ahora tiene permiso mágico de hacerlo para ese Uri, ya que el usuario lo ha concedido específicamente.

No se necesita permiso " WRITE_EXTERNAL_STORAGE " oficial.

Esta forma de obtener permisos de préstamo es parte del Storage Access Framework , y una discusión sobre esto fue hecha en el Big Android BBQ de Ian Lake. Este es un video llamado Forget the Storage Permission: Alternativas para compartir y colaborar que explican los conceptos básicos y cómo lo usas específicamente para evitar el requisito de permiso WRITE_EXTERNAL_STORAGE completo.

Esto no soluciona completamente el problema de permisos de sincronización / asincronización para todos los casos, pero para cualquier tipo de documento externo, o incluso uno que es ofrecido por un proveedor (como gDrive, Box.net, Dropbox, etc.) esto puede ser Una solución que merece la pena echarle un vistazo.

He aquí cómo solucioné el problema de "sincronicidad" sin tener que bloquear explícitamente (y esperar ocupado), o sin requerir una actividad separada de "cargador de arranque". I refactored pantalla de presentación Actividad de la siguiente manera :

Actualización: Un ejemplo más completo se puede encontrar aquí.


Note: Debido a que la API startActivityForResult() llama a startActivityForResult()

 public final void requestPermissions(@NonNull String[] permissions, int requestCode) { Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); } 

La lógica de creación de vista principal se movió de OnCreate() a OnCreate2() y OnCreate() ahora controla la comprobación de permisos. Si RequestPermissions() necesita ser llamado, entonces el OnRequestPermissionsResult() asociado reinicia esta actividad (reenviando una copia del paquete original).


 [Activity(Label = "MarshmellowActivated", MainLauncher = true, Theme = "@style/Theme.Transparent", Icon = "@drawable/icon" //////////////////////////////////////////////////////////////// // THIS PREVENTS OnRequestPermissionsResult() from being called //NoHistory = true )] public class MarshmellowActivated : Activity { private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 112; private Bundle _savedInstanceState; public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case ANDROID_PERMISSION_REQUEST_CODE__SDCARD: if (grantResults.Length > 0 && grantResults[0] == Permission.Granted) { Intent restartThisActivityIntent = new Intent(this, this.GetType()); if (_savedInstanceState != null) { // *ref1: Forward bundle from one intent to another restartThisActivityIntent.PutExtras(_savedInstanceState); } StartActivity(restartThisActivityIntent); } else { throw new Exception("SD Card Write Access: Denied."); } break; } } protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); /////////////////////////////////////////////////////////////////////////////////////////////////////////// // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage); //if(extStoragePerm == Permission.Denied) if (extStoragePerm != Permission.Granted) { _savedInstanceState = savedInstanceState; // **calls startActivityForResult()** RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, ANDROID_PERMISSION_REQUEST_CODE__SDCARD); } else { OnCreate2(savedInstanceState); } } private void OnCreate2(Bundle savedInstanceState) { //... } } 

Ref1: Reenviar paquete de una intención a otra

Note: Esto puede ser refactorizado para manejar más permisos en general. En la actualidad maneja el permiso de escritura sdcard sólo, que debe transmitir la lógica pertinente con suficiente claridad.

Puede agregar un método de ayuda de bloqueo como este:

 @TargetApi(23) public static void checkForPermissionsMAndAboveBlocking(Activity act) { Log.i(Prefs.TAG, "checkForPermissions() called"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Here, thisActivity is the current activity if (act.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // No explanation needed, we can request the permission. act.requestPermissions( new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); while (true) { if (act.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { Log.i(Prefs.TAG, "Got permissions, exiting block loop"); break; } Log.i(Prefs.TAG, "Sleeping, waiting for permissions"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } // permission already granted else { Log.i(Prefs.TAG, "permission already granted"); } } else { Log.i(Prefs.TAG, "Below M, permissions not via code"); } } 
  • ¿Permiso de notificaciones push (GCM) en tiempo de ejecución?
  • ActivityCompat.requestPermissions no muestra el mensaje
  • Actualizar aplicación a Marshmallow o Nougat
  • Android Studio agrega un permiso no deseado después de ejecutar la aplicación en un dispositivo real
  • Los permisos de Android M cierran mi aplicación
  • Permiso de Android.INTERACT_ACROSS_USERS denegación
  • ¿Por qué necesito el permiso WRITE_EXTERNAL_STORAGE con getExternalCacheDir () en Android Lollipop?
  • ¿Cómo verificar permisos de subvenciones en tiempo de ejecución?
  • Grant uri permiso para uri en EXTRA_STREAM en intención
  • Android READ_PHONE_STATE permiso de ejecución solicita realizar y administrar llamadas telefónicas
  • Permiso de Marshmallow: Componente Lifecycle
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.