Offer to grant storage permissions if absent
* java/org/gnu/emacs/EmacsService.java (externalStorageAvailable) (requestStorageAccess23, requestStorageAccess30) (requestStorageAccess): New functions. * lisp/startup.el (fancy-startup-tail, normal-splash-screen): Call android-win functions for inserting the new storage permission notice. * lisp/term/android-win.el (android-display-storage-permission-popup) (android-after-splash-screen): New functions. * src/android.c (android_init_emacs_service): Link to new Java functions. (android_external_storage_available_p) (android_request_storage_access): New functions. * src/android.h: Update prototypes. * src/androidfns.c (Fandroid_external_storage_available_p) (Fandroid_request_storage_access): New functions. (syms_of_androidfns): Register new subrs.
This commit is contained in:
parent
05213345c0
commit
669e754f5b
6 changed files with 314 additions and 1 deletions
|
@ -63,6 +63,7 @@
|
|||
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.Looper;
|
||||
import android.os.IBinder;
|
||||
import android.os.Handler;
|
||||
|
@ -73,6 +74,7 @@
|
|||
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.Settings;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.DisplayMetrics;
|
||||
|
@ -1909,4 +1911,124 @@ In addition, arbitrary runtime exceptions (such as
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Functions for detecting and requesting storage permissions. */
|
||||
|
||||
public boolean
|
||||
externalStorageAvailable ()
|
||||
{
|
||||
final String readPermission;
|
||||
|
||||
readPermission = "android.permission.READ_EXTERNAL_STORAGE";
|
||||
|
||||
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||
? (checkSelfPermission (readPermission)
|
||||
== PackageManager.PERMISSION_GRANTED)
|
||||
: Environment.isExternalStorageManager ());
|
||||
}
|
||||
|
||||
private void
|
||||
requestStorageAccess23 ()
|
||||
{
|
||||
Runnable runnable;
|
||||
|
||||
runnable = new Runnable () {
|
||||
@Override
|
||||
public void
|
||||
run ()
|
||||
{
|
||||
EmacsActivity activity;
|
||||
String permission, permission1;
|
||||
|
||||
permission = "android.permission.READ_EXTERNAL_STORAGE";
|
||||
permission1 = "android.permission.WRITE_EXTERNAL_STORAGE";
|
||||
|
||||
/* Find an activity that is entitled to display a permission
|
||||
request dialog. */
|
||||
|
||||
if (EmacsActivity.focusedActivities.isEmpty ())
|
||||
{
|
||||
/* If focusedActivities is empty then this dialog may
|
||||
have been displayed immediately after another popup
|
||||
dialog was dismissed. Try the EmacsActivity to be
|
||||
focused. */
|
||||
|
||||
activity = EmacsActivity.lastFocusedActivity;
|
||||
|
||||
if (activity == null)
|
||||
{
|
||||
/* Still no luck. Return failure. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
activity = EmacsActivity.focusedActivities.get (0);
|
||||
|
||||
/* Now request these permissions. */
|
||||
activity.requestPermissions (new String[] { permission,
|
||||
permission1, },
|
||||
0);
|
||||
}
|
||||
};
|
||||
|
||||
runOnUiThread (runnable);
|
||||
}
|
||||
|
||||
private void
|
||||
requestStorageAccess30 ()
|
||||
{
|
||||
Runnable runnable;
|
||||
final Intent intent;
|
||||
|
||||
intent
|
||||
= new Intent (Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||
Uri.parse ("package:org.gnu.emacs"));
|
||||
|
||||
runnable = new Runnable () {
|
||||
@Override
|
||||
public void
|
||||
run ()
|
||||
{
|
||||
EmacsActivity activity;
|
||||
|
||||
/* Find an activity that is entitled to display a permission
|
||||
request dialog. */
|
||||
|
||||
if (EmacsActivity.focusedActivities.isEmpty ())
|
||||
{
|
||||
/* If focusedActivities is empty then this dialog may
|
||||
have been displayed immediately after another popup
|
||||
dialog was dismissed. Try the EmacsActivity to be
|
||||
focused. */
|
||||
|
||||
activity = EmacsActivity.lastFocusedActivity;
|
||||
|
||||
if (activity == null)
|
||||
{
|
||||
/* Still no luck. Return failure. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
activity = EmacsActivity.focusedActivities.get (0);
|
||||
|
||||
/* Now request these permissions. */
|
||||
|
||||
activity.startActivity (intent);
|
||||
}
|
||||
};
|
||||
|
||||
runOnUiThread (runnable);
|
||||
}
|
||||
|
||||
public void
|
||||
requestStorageAccess ()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
|
||||
requestStorageAccess23 ();
|
||||
else
|
||||
requestStorageAccess30 ();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2036,7 +2036,10 @@ a face or button specification."
|
|||
(call-interactively
|
||||
'recover-session)))
|
||||
" to recover the files you were editing."))))
|
||||
|
||||
;; Insert the permissions notice if the user has yet to grant Emacs
|
||||
;; storage permissions.
|
||||
(when (fboundp 'android-after-splash-screen)
|
||||
(funcall 'android-after-splash-screen t))
|
||||
(when concise
|
||||
(fancy-splash-insert
|
||||
:face 'variable-pitch "\n"
|
||||
|
@ -2238,6 +2241,11 @@ splash screen in another window."
|
|||
"type M-x recover-session RET\nto recover"
|
||||
" the files you were editing.\n"))
|
||||
|
||||
;; Insert the permissions notice if the user has yet to grant
|
||||
;; Emacs storage permissions.
|
||||
(when (fboundp 'android-after-splash-screen)
|
||||
(funcall 'android-after-splash-screen nil))
|
||||
|
||||
(use-local-map splash-screen-keymap)
|
||||
|
||||
;; Display the input that we set up in the buffer.
|
||||
|
|
|
@ -338,6 +338,92 @@ the `stop-selecting-text' editing key."
|
|||
(global-set-key [start-selecting-text] 'set-mark-command)
|
||||
(global-set-key [stop-selecting-text] 'android-deactivate-mark-command)
|
||||
|
||||
|
||||
;; Splash screen notice. Users are frequently left scratching their
|
||||
;; heads when they overlook the Android appendex in the Emacs manual
|
||||
;; and discover that external storage is not accessible; worse yet,
|
||||
;; Android 11 and later veil the settings panel controlling such
|
||||
;; permissions behind layer upon layer of largely immaterial settings
|
||||
;; panels, such that several modified copies of the Android Settings
|
||||
;; app have omitted them altogether after their developers conducted
|
||||
;; their own interface simplifications. Display a button on the
|
||||
;; splash screen that instructs users on granting these permissions
|
||||
;; when they are denied.
|
||||
|
||||
(declare-function android-external-storage-available-p "androidfns.c")
|
||||
(declare-function android-request-storage-access "androidfns.c")
|
||||
(declare-function android-request-directory-access "androidfns.c")
|
||||
|
||||
(defun android-display-storage-permission-popup (&optional _ignored)
|
||||
"Display a dialog regarding storage permissions.
|
||||
Display a buffer explaining the need for storage permissions and
|
||||
offering to grant them."
|
||||
(interactive)
|
||||
(with-current-buffer (get-buffer-create "*Android Permissions*")
|
||||
(setq buffer-read-only nil)
|
||||
(erase-buffer)
|
||||
(insert (propertize "Storage Access Permissions"
|
||||
'face '(bold (:height 1.2))))
|
||||
(insert "
|
||||
|
||||
Before Emacs can access your device's external storage
|
||||
directories, such as /sdcard and /storage/emulated/0, you must
|
||||
grant it permission to do so.
|
||||
|
||||
Alternatively, you can request access to a particular directory
|
||||
in external storage, whereafter it will be available under the
|
||||
directory /content/storage.
|
||||
|
||||
")
|
||||
(insert-button "Grant storage permissions"
|
||||
'action (lambda (_)
|
||||
(android-request-storage-access)
|
||||
(quit-window)))
|
||||
(newline)
|
||||
(newline)
|
||||
(insert-button "Request access to directory"
|
||||
'action (lambda (_)
|
||||
(android-request-directory-access)))
|
||||
(newline)
|
||||
(special-mode)
|
||||
(setq buffer-read-only t))
|
||||
(let ((window (display-buffer "*Android Permissions*")))
|
||||
(when (windowp window)
|
||||
(with-selected-window window
|
||||
;; Fill the text to the width of this window in columns if it
|
||||
;; does not exceed 72, that the text might not be wrapped or
|
||||
;; truncated.
|
||||
(when (<= (window-width window) 72)
|
||||
(let ((fill-column (window-width window))
|
||||
(inhibit-read-only t))
|
||||
(fill-region (point-min) (point-max))))))))
|
||||
|
||||
(defun android-after-splash-screen (fancy-p)
|
||||
"Insert a brief notice on the absence of storage permissions.
|
||||
If storage permissions are as yet denied to Emacs, insert a short
|
||||
notice to that effect, followed by a button that enables the user
|
||||
to grant such permissions.
|
||||
|
||||
FANCY-P controls if the inserted notice should be displayed in a
|
||||
variable space consequent on its being incorporated within the
|
||||
fancy splash screen."
|
||||
(unless (android-external-storage-available-p)
|
||||
(if fancy-p
|
||||
(fancy-splash-insert
|
||||
:face '(variable-pitch
|
||||
font-lock-function-call-face)
|
||||
"\nPermissions necessary to access external storage directories have
|
||||
been denied. Click "
|
||||
:link '("here" android-display-storage-permission-popup)
|
||||
" to grant them.")
|
||||
(insert
|
||||
"Permissions necessary to access external storage directories have been
|
||||
denied. ")
|
||||
(insert-button "Click here to grant them."
|
||||
'action #'android-display-storage-permission-popup
|
||||
'follow-link t)
|
||||
(newline))))
|
||||
|
||||
|
||||
(provide 'android-win)
|
||||
;; android-win.el ends here.
|
||||
|
|
|
@ -1628,6 +1628,10 @@ android_init_emacs_service (void)
|
|||
"Ljava/lang/String;)Ljava/lang/String;");
|
||||
FIND_METHOD (valid_authority, "validAuthority",
|
||||
"(Ljava/lang/String;)Z");
|
||||
FIND_METHOD (external_storage_available,
|
||||
"externalStorageAvailable", "()Z");
|
||||
FIND_METHOD (request_storage_access,
|
||||
"requestStorageAccess", "()V");
|
||||
#undef FIND_METHOD
|
||||
}
|
||||
|
||||
|
@ -6558,6 +6562,57 @@ android_request_directory_access (void)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Return whether Emacs is entitled to access external storage.
|
||||
|
||||
On Android 5.1 and earlier, such permissions as are declared within
|
||||
an application's manifest are granted during installation and are
|
||||
irrevocable.
|
||||
|
||||
On Android 6.0 through Android 10.0, the right to read external
|
||||
storage is a regular permission granted from the Permissions
|
||||
panel.
|
||||
|
||||
On Android 11.0 and later, that right must be granted through an
|
||||
independent ``Special App Access'' settings panel. */
|
||||
|
||||
bool
|
||||
android_external_storage_available_p (void)
|
||||
{
|
||||
jboolean rc;
|
||||
jmethodID method;
|
||||
|
||||
if (android_api_level <= 22) /* LOLLIPOP_MR1 */
|
||||
return true;
|
||||
|
||||
method = service_class.external_storage_available;
|
||||
rc = (*android_java_env)->CallNonvirtualBooleanMethod (android_java_env,
|
||||
emacs_service,
|
||||
service_class.class,
|
||||
method);
|
||||
android_exception_check ();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Display a dialog from which the aforementioned rights can be
|
||||
granted. */
|
||||
|
||||
void
|
||||
android_request_storage_access (void)
|
||||
{
|
||||
jmethodID method;
|
||||
|
||||
if (android_api_level <= 22) /* LOLLIPOP_MR1 */
|
||||
return;
|
||||
|
||||
method = service_class.request_storage_access;
|
||||
(*android_java_env)->CallNonvirtualVoidMethod (android_java_env,
|
||||
emacs_service,
|
||||
service_class.class,
|
||||
method);
|
||||
android_exception_check ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* The thread from which a query against a thread is currently being
|
||||
|
|
|
@ -123,6 +123,8 @@ extern void android_wait_event (void);
|
|||
extern void android_toggle_on_screen_keyboard (android_window, bool);
|
||||
extern _Noreturn void android_restart_emacs (void);
|
||||
extern int android_request_directory_access (void);
|
||||
extern bool android_external_storage_available_p (void);
|
||||
extern void android_request_storage_access (void);
|
||||
extern int android_get_current_api_level (void)
|
||||
__attribute__ ((pure));
|
||||
|
||||
|
@ -289,6 +291,8 @@ struct android_emacs_service
|
|||
jmethodID rename_document;
|
||||
jmethodID move_document;
|
||||
jmethodID valid_authority;
|
||||
jmethodID external_storage_available;
|
||||
jmethodID request_storage_access;
|
||||
};
|
||||
|
||||
extern JNIEnv *android_java_env;
|
||||
|
|
|
@ -3096,6 +3096,42 @@ within the directory `/content/storage'. */)
|
|||
|
||||
|
||||
|
||||
/* Functions concerning storage permissions. */
|
||||
|
||||
DEFUN ("android-external-storage-available-p",
|
||||
Fandroid_external_storage_available_p,
|
||||
Sandroid_external_storage_available_p, 0, 0, 0,
|
||||
doc: /* Return whether Emacs is entitled to access external storage.
|
||||
Return nil if the requisite permissions for external storage access
|
||||
have not been granted to Emacs, t otherwise. Such permissions can be
|
||||
requested by means of the `android-request-storage-access'
|
||||
command.
|
||||
|
||||
External storage on Android encompasses the `/sdcard' and
|
||||
`/storage/emulated' directories, access to which is denied to programs
|
||||
absent these permissions. */)
|
||||
(void)
|
||||
{
|
||||
return android_external_storage_available_p () ? Qt : Qnil;
|
||||
}
|
||||
|
||||
DEFUN ("android-request-storage-access", Fandroid_request_storage_access,
|
||||
Sandroid_request_storage_access, 0, 0, "",
|
||||
doc: /* Request rights to access external storage.
|
||||
|
||||
Return nil whether access is accorded or not, immediately subsequent
|
||||
to displaying the permissions request dialog.
|
||||
|
||||
`android-external-storage-available-p' (which see) ascertains if Emacs
|
||||
has received such rights. */)
|
||||
(void)
|
||||
{
|
||||
android_request_storage_access ();
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Miscellaneous input method related stuff. */
|
||||
|
||||
/* Report X, Y, by the phys cursor width and height as the cursor
|
||||
|
@ -3302,6 +3338,8 @@ bell being rung. */);
|
|||
#ifndef ANDROID_STUBIFY
|
||||
defsubr (&Sandroid_query_battery);
|
||||
defsubr (&Sandroid_request_directory_access);
|
||||
defsubr (&Sandroid_external_storage_available_p);
|
||||
defsubr (&Sandroid_request_storage_access);
|
||||
|
||||
tip_timer = Qnil;
|
||||
staticpro (&tip_timer);
|
||||
|
|
Loading…
Add table
Reference in a new issue