summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/AndroidManifest.xml.in137
-rw-r--r--java/INSTALL34
-rw-r--r--java/Makefile.in13
-rwxr-xr-xjava/debug.sh13
-rw-r--r--java/org/gnu/emacs/EmacsActivity.java129
-rw-r--r--java/org/gnu/emacs/EmacsContextMenu.java17
-rw-r--r--java/org/gnu/emacs/EmacsDesktopNotification.java202
-rw-r--r--java/org/gnu/emacs/EmacsNative.java22
-rw-r--r--java/org/gnu/emacs/EmacsOpenActivity.java43
-rw-r--r--java/org/gnu/emacs/EmacsPreferencesActivity.java5
-rw-r--r--java/org/gnu/emacs/EmacsService.java254
-rw-r--r--java/org/gnu/emacs/EmacsView.java33
-rw-r--r--java/org/gnu/emacs/EmacsWindow.java159
-rw-r--r--java/org/gnu/emacs/EmacsWindowAttachmentManager.java9
14 files changed, 859 insertions, 211 deletions
diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in
index b18446bece0..563914fb02c 100644
--- a/java/AndroidManifest.xml.in
+++ b/java/AndroidManifest.xml.in
@@ -64,6 +64,132 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+ <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
+ <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
+ <uses-permission android:name="android.permission.MANAGE_IPSEC_TUNNELS" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA" />
+ <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS" />
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+ <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+ <uses-permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS" />
+ <uses-permission android:name="android.permission.TURN_SCREEN_ON" />
+ <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+ <uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER" />
+ <uses-permission android:name="android.permission.USE_SIP" />
+ <uses-permission android:name="android.permission.UWB_RANGING" />
+ <uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
+ <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
+ <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS" />
+ <uses-permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN" />
+ <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
+ <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
+ <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+ <uses-permission android:name="android.permission.FLASHLIGHT" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+ <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
+ <uses-permission android:name="android.permission.GET_TASKS" />
+ <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
+ <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
+ <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO" />
+ <uses-permission android:name="android.permission.NFC_TRANSACTION_EVENT" />
+ <uses-permission android:name="android.permission.PERSISTENT_ACTIVITY" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
+ <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.READ_SOCIAL_STREAM" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.REORDER_TASKS" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
+ <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+ <uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
+ <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY" />
+ <uses-permission android:name="android.permission.RESTART_PACKAGES" />
+ <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+ <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" />
+ <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" />
+ <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
+ <uses-permission android:name="android.permission.USE_BIOMETRIC" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <uses-permission android:name="android.permission.USE_FINGERPRINT" />
+ <uses-permission android:name="android.permission.WRITE_PROFILE" />
+ <uses-permission android:name="android.permission.WRITE_SMS" />
+ <uses-permission android:name="android.permission.WRITE_SOCIAL_STREAM" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
+
<!-- This is required on Android 11 or later to access /sdcard. -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
@@ -92,6 +218,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
<activity android:name="org.gnu.emacs.EmacsActivity"
android:launchMode="singleInstance"
+ android:taskAffinity="emacs.primary_frame"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|locale|fontScale">
@@ -103,7 +230,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
</activity>
<activity android:name="org.gnu.emacs.EmacsOpenActivity"
- android:taskAffinity="open.dialog"
+ android:taskAffinity="emacs.open_dialog"
android:excludeFromRecents="true"
android:exported="true">
@@ -147,6 +274,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
</activity>
<activity android:name="org.gnu.emacs.EmacsMultitaskActivity"
+ android:taskAffinity="emacs.secondary_frame"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|locale|fontScale"/>
@@ -190,6 +318,13 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
</intent-filter>
</provider>
+ <receiver android:name=".EmacsDesktopNotification$CancellationReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="org.gnu.emacs.DISMISSED" />
+ </intent-filter>
+ </receiver>
+
<service android:name="org.gnu.emacs.EmacsService"
android:directBootAware="false"
android:enabled="true"
diff --git a/java/INSTALL b/java/INSTALL
index 175ff2826b2..f1063b40c25 100644
--- a/java/INSTALL
+++ b/java/INSTALL
@@ -166,25 +166,21 @@ than a compressed package for a newer version of Android.
BUILDING C++ DEPENDENCIES
-With a new version of the NDK, dependencies containing C++ code should
-build without any further configuration. However, older versions
-require that you use the ``make_standalone_toolchain.py'' script in
-the NDK distribution to create a ``standalone toolchain'', and use
-that instead, in order for C++ headers to be found.
-
-See https://developer.android.com/ndk/guides/standalone_toolchain for
-more details; when a ``standalone toolchain'' is specified, the
-configure script will try to determine the location of the C++
-compiler based on the C compiler specified. If that automatic
-detection does not work, you can specify a C++ compiler yourself, like
-so:
-
- ./configure --with-ndk-cxx=/path/to/toolchain/bin/i686-linux-android-g++
-
-Some versions of the NDK have a bug, where GCC fails to locate
-``stddef.h'' after being copied to a standalone toolchain. To work
-around this problem (which normally exhibits itself when building C++
-code), add:
+In normal circumstances, Emacs should automatically detect and configure
+one of the C++ standard libraries part of the NDK when such a library is
+required to build a dependency specified under `--with-ndk-path'.
+
+Nevertheless, this process is not infalliable, and with certain versions
+of the NDK is liable to fail to locate a C++ compiler, requiring that
+you run the `make_standalone_toolchain.py' script in the NDK
+distribution to create a ``standalone toolchain'' and substitute the
+same for the regular compiler toolchain. See
+https://developer.android.com/ndk/guides/standalone_toolchain for
+further details.
+
+Some versions of the NDK that ship GCC 4.9.x exhibit a bug where the
+compiler cannot locate `stddef.h' after being copied to a standalone
+toolchain. To work around this problem, add:
-isystem /path/to/toolchain/include/c++/4.9.x
diff --git a/java/Makefile.in b/java/Makefile.in
index 60bd2ea086b..c23b52ed44e 100644
--- a/java/Makefile.in
+++ b/java/Makefile.in
@@ -256,15 +256,15 @@ install_temp/assets/build_info: install_temp
emacs.apk-in: install_temp install_temp/assets/directory-tree \
AndroidManifest.xml install_temp/assets/version \
- install_temp/assets/build_info
-# Package everything. Specifying the assets on this command line is
-# necessary for AAssetManager_getNextFileName to work on old versions
-# of Android. Make sure not to generate R.java, as it's already been
-# generated.
+ install_temp/assets/build_info classes.dex
+# Package everything. Redirect the generated R.java to install_temp, as
+# it must already have been generated as a prerequisite of
+# classes.dex's.
$(AM_V_AAPT) $(AAPT) p -I "$(ANDROID_JAR)" -F $@ \
-f -M AndroidManifest.xml $(AAPT_ASSET_ARGS) \
-A install_temp/assets \
-S $(top_srcdir)/java/res -J install_temp
+ $(AM_V_SILENT) $(AAPT) a $@ classes.dex
$(AM_V_SILENT) pushd install_temp &> /dev/null; \
$(AAPT) add ../$@ `find lib -type f`; \
popd &> /dev/null
@@ -311,10 +311,9 @@ classes.dex: $(CLASS_FILES)
.PHONY: clean maintainer-clean
-$(APK_NAME): classes.dex emacs.apk-in $(srcdir)/emacs.keystore
+$(APK_NAME): emacs.apk-in $(srcdir)/emacs.keystore
$(AM_V_GEN)
$(AM_V_SILENT) cp -f emacs.apk-in $@.unaligned
- $(AM_V_SILENT) $(AAPT) add $@.unaligned classes.dex
$(AM_V_SILENT) $(JARSIGNER) $(SIGN_EMACS) $@.unaligned "Emacs keystore"
$(AM_V_SILENT) $(ZIPALIGN) -f 4 $@.unaligned $@
# Signing must happen after alignment!
diff --git a/java/debug.sh b/java/debug.sh
index 8fc03d014cf..c5d40141355 100755
--- a/java/debug.sh
+++ b/java/debug.sh
@@ -104,13 +104,14 @@ if [ -z "$devices" ]; then
exit 1
fi
-if [ -z $device ]; then
- device=$devices
+if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z $device ]; then
+ echo "Multiple devices are available. Please specify one with"
+ echo "the option --device and try again."
+ exit 1
fi
-if [ `wc -w <<< "$devices"` -gt 1 ] && [ -z device ]; then
- echo "Multiple devices are available. Please pick one using"
- echo "--device and try again."
+if [ -z $device ]; then
+ device=$devices
fi
echo "Looking for $package on device $device"
@@ -189,6 +190,8 @@ if [ "$attach_existing" != "yes" ]; then
package_pids=`awk -f tmp.awk <<< $package_pids`
fi
+rm tmp.awk
+
pid=$package_pids
num_pids=`wc -w <<< "$package_pids"`
diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java
index 3237f650240..e380b7bfc2a 100644
--- a/java/org/gnu/emacs/EmacsActivity.java
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -20,9 +20,12 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
package org.gnu.emacs;
import java.lang.IllegalStateException;
+
import java.util.List;
import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
import android.app.Activity;
import android.content.ContentResolver;
@@ -31,6 +34,7 @@ import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.util.Log;
@@ -78,13 +82,16 @@ public class EmacsActivity extends Activity
/* The last context menu to be closed. */
private static Menu lastClosedMenu;
+ /* The time of the most recent call to onStop. */
+ private static long timeOfLastInteraction;
+
static
{
focusedActivities = new ArrayList<EmacsActivity> ();
};
public static void
- invalidateFocus1 (EmacsWindow window)
+ invalidateFocus1 (EmacsWindow window, boolean resetWhenChildless)
{
if (window.view.isFocused ())
focusedWindow = window;
@@ -92,12 +99,23 @@ public class EmacsActivity extends Activity
synchronized (window.children)
{
for (EmacsWindow child : window.children)
- invalidateFocus1 (child);
+ invalidateFocus1 (child, false);
+
+ /* If no focused window was previously detected among WINDOW's
+ children and RESETWHENCHILDLESS is set (implying it is a
+ toplevel window), request that it be focused, to avoid
+ creating a situation where no windows exist focused or can be
+ transferred the input focus by user action. */
+ if (focusedWindow == null && resetWhenChildless)
+ {
+ window.view.requestFocus ();
+ focusedWindow = window;
+ }
}
}
public static void
- invalidateFocus ()
+ invalidateFocus (int whence)
{
EmacsWindow oldFocus;
@@ -110,7 +128,7 @@ public class EmacsActivity extends Activity
for (EmacsActivity activity : focusedActivities)
{
if (activity.window != null)
- invalidateFocus1 (activity.window);
+ invalidateFocus1 (activity.window, focusedWindow == null);
}
/* Send focus in- and out- events to the previous and current
@@ -144,7 +162,7 @@ public class EmacsActivity extends Activity
layout.removeView (window.view);
window = null;
- invalidateFocus ();
+ invalidateFocus (0);
}
}
@@ -172,8 +190,17 @@ public class EmacsActivity extends Activity
if (isPaused)
window.noticeIconified ();
- /* Invalidate the focus. */
- invalidateFocus ();
+ /* Invalidate the focus. Since attachWindow may be called from
+ either the main or the UI thread, post this to the UI thread. */
+
+ runOnUiThread (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ invalidateFocus (1);
+ }
+ });
}
@Override
@@ -238,6 +265,10 @@ public class EmacsActivity extends Activity
}
super.onCreate (savedInstanceState);
+
+ /* Call `onWindowFocusChanged' to read the focus state, which fails
+ to be called after an activity is recreated. */
+ onWindowFocusChanged (false);
}
@Override
@@ -249,6 +280,50 @@ public class EmacsActivity extends Activity
@Override
public final void
+ onStop ()
+ {
+ timeOfLastInteraction = SystemClock.elapsedRealtime ();
+
+ super.onStop ();
+ }
+
+ /* Return whether the task is being finished in response to explicit
+ user action. That is to say, Activity.isFinished, but as
+ documented. */
+
+ public final boolean
+ isReallyFinishing ()
+ {
+ long atime, dtime;
+ int hours;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ return isFinishing ();
+
+ /* When the number of tasks retained in the recents list exceeds a
+ threshold, Android 7 and later so destroy activities in trimming
+ them from recents on the expiry of a timeout that isFinishing
+ returns true, in direct contradiction to the documentation. This
+ timeout is generally 6 hours, but admits of customization by
+ individual system distributors, so to err on the side of the
+ caution, the timeout Emacs applies is a more conservative figure
+ of 4 hours. */
+
+ if (timeOfLastInteraction == 0)
+ return isFinishing ();
+
+ atime = timeOfLastInteraction;
+
+ /* Compare atime with the current system time. */
+ dtime = SystemClock.elapsedRealtime () - atime;
+ if (dtime + 1000000 < TimeUnit.HOURS.toMillis (4))
+ return isFinishing ();
+
+ return false;
+ }
+
+ @Override
+ public final void
onDestroy ()
{
EmacsWindowAttachmentManager manager;
@@ -259,9 +334,10 @@ public class EmacsActivity extends Activity
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
isMultitask = this instanceof EmacsMultitaskActivity;
- manager.removeWindowConsumer (this, isMultitask || isFinishing ());
+ manager.removeWindowConsumer (this, (isMultitask
+ || isReallyFinishing ()));
focusedActivities.remove (this);
- invalidateFocus ();
+ invalidateFocus (2);
/* Remove this activity from the static field, lest it leak. */
if (lastFocusedActivity == this)
@@ -274,9 +350,16 @@ public class EmacsActivity extends Activity
public final void
onWindowFocusChanged (boolean isFocused)
{
- if (isFocused && !focusedActivities.contains (this))
+ /* At times and on certain versions of Android ISFOCUSED does not
+ reflect whether the window actually holds focus, so replace it
+ with the value of `hasWindowFocus'. */
+ isFocused = hasWindowFocus ();
+
+ if (isFocused)
{
- focusedActivities.add (this);
+ if (!focusedActivities.contains (this))
+ focusedActivities.add (this);
+
lastFocusedActivity = this;
/* Update the window insets as the focus change may have
@@ -291,7 +374,7 @@ public class EmacsActivity extends Activity
else
focusedActivities.remove (this);
- invalidateFocus ();
+ invalidateFocus (3);
}
@Override
@@ -309,6 +392,7 @@ public class EmacsActivity extends Activity
onResume ()
{
isPaused = false;
+ timeOfLastInteraction = 0;
EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this);
super.onResume ();
@@ -433,6 +517,27 @@ public class EmacsActivity extends Activity
syncFullscreenWith (window);
}
+ @Override
+ public final void
+ onNewIntent (Intent intent)
+ {
+ String tag, action;
+
+ /* This function is called when EmacsActivity is relaunched from a
+ notification. */
+
+ if (intent == null || EmacsService.SERVICE == null)
+ return;
+
+ tag = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_TAG);
+ action
+ = intent.getStringExtra (EmacsDesktopNotification.NOTIFICATION_ACTION);
+
+ if (tag == null || action == null)
+ return;
+
+ EmacsNative.sendNotificationAction (tag, action);
+ }
@Override
diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java
index 17e6033377d..2bbf2a313d6 100644
--- a/java/org/gnu/emacs/EmacsContextMenu.java
+++ b/java/org/gnu/emacs/EmacsContextMenu.java
@@ -361,8 +361,23 @@ public final class EmacsContextMenu
public Boolean
call ()
{
+ boolean rc;
+
lastMenuEventSerial = serial;
- return display1 (window, xPosition, yPosition);
+ rc = display1 (window, xPosition, yPosition);
+
+ /* Android 3.0 to Android 7.0 perform duplicate calls to
+ onContextMenuClosed the second time a context menu is
+ dismissed. Since the second call after such a dismissal is
+ otherwise liable to prematurely cancel any context menu
+ displayed immediately afterwards, ignore calls received
+ within 150 milliseconds of this menu's being displayed. */
+
+ if (rc && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
+ && Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ wasSubmenuSelected = System.currentTimeMillis () - 150;
+
+ return rc;
}
});
diff --git a/java/org/gnu/emacs/EmacsDesktopNotification.java b/java/org/gnu/emacs/EmacsDesktopNotification.java
index fb35e3fea1f..72569631a8c 100644
--- a/java/org/gnu/emacs/EmacsDesktopNotification.java
+++ b/java/org/gnu/emacs/EmacsDesktopNotification.java
@@ -24,9 +24,12 @@ import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
+
import android.os.Build;
import android.widget.RemoteViews;
@@ -44,6 +47,16 @@ import android.widget.RemoteViews;
public final class EmacsDesktopNotification
{
+ /* Intent tag for notification action data. */
+ public static final String NOTIFICATION_ACTION = "emacs:notification_action";
+
+ /* Intent tag for notification IDs. */
+ public static final String NOTIFICATION_TAG = "emacs:notification_tag";
+
+ /* Action ID assigned to the broadcast receiver which should be
+ notified of any notification's being dismissed. */
+ public static final String NOTIFICATION_DISMISSED = "org.gnu.emacs.DISMISSED";
+
/* The content of this desktop notification. */
public final String content;
@@ -66,10 +79,20 @@ public final class EmacsDesktopNotification
/* The importance of this notification's group. */
public final int importance;
+ /* Array of actions and their user-facing text to be offered by this
+ notification. */
+ public final String[] actions, titles;
+
+ /* Delay in miliseconds after which this notification should be
+ automatically dismissed. */
+ public final long delay;
+
public
EmacsDesktopNotification (String title, String content,
String group, String tag, int icon,
- int importance)
+ int importance,
+ String[] actions, String[] titles,
+ long delay)
{
this.content = content;
this.title = title;
@@ -77,12 +100,69 @@ public final class EmacsDesktopNotification
this.tag = tag;
this.icon = icon;
this.importance = importance;
+ this.actions = actions;
+ this.titles = titles;
+ this.delay = delay;
}
/* Functions for displaying desktop notifications. */
+ /* Insert each action in actions and titles into the notification
+ builder BUILDER, with pending intents created with CONTEXT holding
+ suitable metadata. */
+
+ @SuppressWarnings ("deprecation")
+ private void
+ insertActions (Context context, Notification.Builder builder)
+ {
+ int i;
+ PendingIntent pending;
+ Intent intent;
+ Notification.Action.Builder action;
+
+ if (actions == null)
+ return;
+
+ for (i = 0; i < actions.length; ++i)
+ {
+ /* Actions named default should not be displayed. */
+ if (actions[i].equals ("default"))
+ continue;
+
+ intent = new Intent (context, EmacsActivity.class);
+ intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ /* Pending intents are specific to combinations of class, action
+ and data, but not information provided as extras. In order
+ that its target may be invoked with the action and tag set
+ below, generate a URL from those two elements and specify it
+ as the intent data, which ensures that the intent allocated
+ fully reflects the duo. */
+
+ intent.setData (new Uri.Builder ().scheme ("action")
+ .appendPath (tag).appendPath (actions[i])
+ .build ());
+ intent.putExtra (NOTIFICATION_ACTION, actions[i]);
+ intent.putExtra (NOTIFICATION_TAG, tag);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ pending = PendingIntent.getActivity (context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ else
+ pending = PendingIntent.getActivity (context, 0, intent, 0);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ {
+ action = new Notification.Action.Builder (0, titles[i], pending);
+ builder.addAction (action.build ());
+ }
+ else
+ builder.addAction (0, titles[i], pending);
+ }
+ }
+
/* Internal helper for `display' executed on the main thread. */
@SuppressWarnings ("deprecation") /* Notification.Builder (Context). */
@@ -97,6 +177,7 @@ public final class EmacsDesktopNotification
Intent intent;
PendingIntent pending;
int priority;
+ Notification.Builder builder;
tem = context.getSystemService (Context.NOTIFICATION_SERVICE);
manager = (NotificationManager) tem;
@@ -108,13 +189,18 @@ public final class EmacsDesktopNotification
(such as its importance) will be overridden. */
channel = new NotificationChannel (group, group, importance);
manager.createNotificationChannel (channel);
+ builder = new Notification.Builder (context, group);
- /* Create a notification object and display it. */
- notification = (new Notification.Builder (context, group)
- .setContentTitle (title)
- .setContentText (content)
- .setSmallIcon (icon)
- .build ());
+ /* Create and configure a notification object and display
+ it. */
+
+ builder.setContentTitle (title);
+ builder.setContentText (content);
+ builder.setSmallIcon (icon);
+ builder.setTimeoutAfter (delay);
+
+ insertActions (context, builder);
+ notification = builder.build ();
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
@@ -122,31 +208,35 @@ public final class EmacsDesktopNotification
distinct categories, but permit an importance to be
assigned to each individual notification. */
- switch (importance)
+ builder = new Notification.Builder (context);
+ builder.setContentTitle (title);
+ builder.setContentText (content);
+ builder.setSmallIcon (icon);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
- case 2: /* IMPORTANCE_LOW */
- default:
- priority = Notification.PRIORITY_LOW;
- break;
-
- case 3: /* IMPORTANCE_DEFAULT */
- priority = Notification.PRIORITY_DEFAULT;
- break;
-
- case 4: /* IMPORTANCE_HIGH */
- priority = Notification.PRIORITY_HIGH;
- break;
+ switch (importance)
+ {
+ case 2: /* IMPORTANCE_LOW */
+ default:
+ priority = Notification.PRIORITY_LOW;
+ break;
+
+ case 3: /* IMPORTANCE_DEFAULT */
+ priority = Notification.PRIORITY_DEFAULT;
+ break;
+
+ case 4: /* IMPORTANCE_HIGH */
+ priority = Notification.PRIORITY_HIGH;
+ break;
+ }
+
+ builder.setPriority (priority);
+ insertActions (context, builder);
+ notification = builder.build ();
}
-
- notification = (new Notification.Builder (context)
- .setContentTitle (title)
- .setContentText (content)
- .setSmallIcon (icon)
- .setPriority (priority)
- .build ());
-
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN)
- notification.priority = priority;
+ else
+ notification = builder.getNotification ();
}
else
{
@@ -170,6 +260,12 @@ public final class EmacsDesktopNotification
intent = new Intent (context, EmacsActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setData (new Uri.Builder ()
+ .scheme ("action")
+ .appendPath (tag)
+ .build ());
+ intent.putExtra (NOTIFICATION_ACTION, "default");
+ intent.putExtra (NOTIFICATION_TAG, tag);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
pending = PendingIntent.getActivity (context, 0, intent,
@@ -179,6 +275,25 @@ public final class EmacsDesktopNotification
notification.contentIntent = pending;
+ /* Provide a cancellation intent to respond to notification
+ dismissals. */
+
+ intent = new Intent (context, CancellationReceiver.class);
+ intent.setAction (NOTIFICATION_DISMISSED);
+ intent.setPackage ("org.gnu.emacs");
+ intent.setData (new Uri.Builder ()
+ .scheme ("action")
+ .appendPath (tag)
+ .build ());
+ intent.putExtra (NOTIFICATION_TAG, tag);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ pending = PendingIntent.getBroadcast (context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ else
+ pending = PendingIntent.getBroadcast (context, 0, intent, 0);
+
+ notification.deleteIntent = pending;
manager.notify (tag, 2, notification);
}
@@ -199,4 +314,31 @@ public final class EmacsDesktopNotification
}
});
}
+
+
+
+ /* Broadcast receiver. This is something of a system-wide callback
+ arranged to be invoked whenever a notification posted by Emacs is
+ dismissed, in order to relay news of its dismissal to
+ androidselect.c and run or remove callbacks as appropriate. */
+
+ public static class CancellationReceiver extends BroadcastReceiver
+ {
+ @Override
+ public void
+ onReceive (Context context, Intent intent)
+ {
+ String tag, action;
+
+ if (intent == null || EmacsService.SERVICE == null)
+ return;
+
+ tag = intent.getStringExtra (NOTIFICATION_TAG);
+
+ if (tag == null)
+ return;
+
+ EmacsNative.sendNotificationDeleted (tag);
+ }
+ };
};
diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java
index cd0e70923d1..654e94b1a7d 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -196,6 +196,12 @@ public final class EmacsNative
public static native long sendDndText (short window, int x, int y,
String text);
+ /* Send an ANDROID_NOTIFICATION_CANCELED event. */
+ public static native void sendNotificationDeleted (String tag);
+
+ /* Send an ANDROID_NOTIFICATION_ACTION event. */
+ public static native void sendNotificationAction (String tag, String action);
+
/* Return the file name associated with the specified file
descriptor, or NULL if there is none. */
public static native byte[] getProcName (int fd);
@@ -275,7 +281,7 @@ public final class EmacsNative
public static native int[] getSelection (short window);
- /* Graphics functions used as a replacement for potentially buggy
+ /* Graphics functions used as replacements for potentially buggy
Android APIs. */
public static native void blitRect (Bitmap src, Bitmap dest, int x1,
@@ -283,7 +289,6 @@ public final class EmacsNative
/* Increment the generation ID of the specified BITMAP, forcing its
texture to be re-uploaded to the GPU. */
-
public static native void notifyPixelsChanged (Bitmap bitmap);
@@ -307,6 +312,13 @@ public final class EmacsNative
in the process. */
public static native boolean ftruncate (int fd);
+
+ /* Functions that assist in generating content file names. */
+
+ /* Calculate an 8 digit checksum for the byte array DISPLAYNAME
+ suitable for inclusion in a content file name. */
+ public static native String displayNameHash (byte[] displayName);
+
static
{
/* Older versions of Android cannot link correctly with shared
@@ -317,7 +329,9 @@ public final class EmacsNative
Every time you add a new shared library dependency to Emacs,
please add it here as well. */
- libraryDeps = new String[] { "png_emacs", "selinux_emacs",
+ libraryDeps = new String[] { "c++_shared", "gnustl_shared",
+ "stlport_shared", "gabi++_shared",
+ "png_emacs", "selinux_emacs",
"crypto_emacs", "pcre_emacs",
"packagelistparser_emacs",
"gnutls_emacs", "gmp_emacs",
@@ -325,7 +339,7 @@ public final class EmacsNative
"tasn1_emacs", "hogweed_emacs",
"jansson_emacs", "jpeg_emacs",
"tiff_emacs", "xml2_emacs",
- "icuuc_emacs",
+ "icuuc_emacs", "harfbuzz_emacs",
"tree-sitter_emacs", };
for (String dependency : libraryDeps)
diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java
index 9ae1bf353dd..327a53bc417 100644
--- a/java/org/gnu/emacs/EmacsOpenActivity.java
+++ b/java/org/gnu/emacs/EmacsOpenActivity.java
@@ -252,7 +252,7 @@ public final class EmacsOpenActivity extends Activity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
- content = EmacsService.buildContentName (uri);
+ content = EmacsService.buildContentName (uri, getContentResolver ());
return content;
}
@@ -423,6 +423,7 @@ public final class EmacsOpenActivity extends Activity
/* Obtain the intent that started Emacs. */
intent = getIntent ();
action = intent.getAction ();
+ resolver = getContentResolver ();
if (action == null)
{
@@ -534,9 +535,19 @@ public final class EmacsOpenActivity extends Activity
uri = intent.getParcelableExtra (Intent.EXTRA_STREAM);
if ((scheme = uri.getScheme ()) != null
- && scheme.equals ("content"))
+ && scheme.equals ("content")
+ && (Build.VERSION.SDK_INT
+ >= Build.VERSION_CODES.KITKAT))
{
- tem1 = EmacsService.buildContentName (uri);
+ tem1 = EmacsService.buildContentName (uri, resolver);
+ attachmentString = ("'(\"" + (tem1.replace ("\\", "\\\\")
+ .replace ("\"", "\\\"")
+ .replace ("$", "\\$"))
+ + "\")");
+ }
+ else if (scheme != null && scheme.equals ("file"))
+ {
+ tem1 = uri.getPath ();
attachmentString = ("'(\"" + (tem1.replace ("\\", "\\\\")
.replace ("\"", "\\\"")
.replace ("$", "\\$"))
@@ -566,9 +577,22 @@ public final class EmacsOpenActivity extends Activity
if (uri != null
&& (scheme = uri.getScheme ()) != null
- && scheme.equals ("content"))
+ && scheme.equals ("content")
+ && (Build.VERSION.SDK_INT
+ >= Build.VERSION_CODES.KITKAT))
+ {
+ tem1
+ = EmacsService.buildContentName (uri, resolver);
+ builder.append ("\"");
+ builder.append (tem1.replace ("\\", "\\\\")
+ .replace ("\"", "\\\"")
+ .replace ("$", "\\$"));
+ builder.append ("\"");
+ }
+ else if (scheme != null
+ && scheme.equals ("file"))
{
- tem1 = EmacsService.buildContentName (uri);
+ tem1 = uri.getPath ();
builder.append ("\"");
builder.append (tem1.replace ("\\", "\\\\")
.replace ("\"", "\\\"")
@@ -602,14 +626,19 @@ public final class EmacsOpenActivity extends Activity
{
fileName = null;
- if (scheme.equals ("content"))
+ if (scheme.equals ("content")
+ /* Retrieving the native file descriptor of a
+ ParcelFileDescriptor requires Honeycomb, and
+ proceeding without this capability is pointless on
+ systems before KitKat, since Emacs doesn't support
+ opening content files on those. */
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
/* This is one of the annoying Android ``content''
URIs. Most of the time, there is actually an
underlying file, but it cannot be found without
opening the file and doing readlink on its file
descriptor in /proc/self/fd. */
- resolver = getContentResolver ();
fd = null;
try
diff --git a/java/org/gnu/emacs/EmacsPreferencesActivity.java b/java/org/gnu/emacs/EmacsPreferencesActivity.java
index 330adbea223..766e2e11d46 100644
--- a/java/org/gnu/emacs/EmacsPreferencesActivity.java
+++ b/java/org/gnu/emacs/EmacsPreferencesActivity.java
@@ -38,8 +38,9 @@ import android.preference.*;
option, which would not be possible otherwise, as there is no
command line on Android.
- Android provides a preferences activity, but it is deprecated.
- Unfortunately, there is no alternative that looks the same way. */
+ This file extends a deprecated preferences activity, but no suitable
+ alternative exists that is identical in appearance to system settings
+ forms. */
@SuppressWarnings ("deprecation")
public class EmacsPreferencesActivity extends PreferenceActivity
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index 5cb1ceca0aa..446cd26a3dd 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -19,6 +19,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
package org.gnu.emacs;
+import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -45,9 +46,11 @@ import android.view.KeyEvent;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.ExtractedText;
+import android.app.AlarmManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.ClipboardManager;
@@ -60,6 +63,7 @@ import android.content.UriPermission;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.content.res.Configuration;
import android.hardware.input.InputManager;
@@ -78,6 +82,7 @@ import android.os.VibrationEffect;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.provider.OpenableColumns;
import android.provider.Settings;
import android.util.Log;
@@ -109,9 +114,10 @@ public final class EmacsService extends Service
private ContentResolver resolver;
/* Keep this in synch with androidgui.h. */
- public static final int IC_MODE_NULL = 0;
- public static final int IC_MODE_ACTION = 1;
- public static final int IC_MODE_TEXT = 2;
+ public static final int IC_MODE_NULL = 0;
+ public static final int IC_MODE_ACTION = 1;
+ public static final int IC_MODE_TEXT = 2;
+ public static final int IC_MODE_PASSWORD = 3;
/* Display metrics used by font backends. */
public DisplayMetrics metrics;
@@ -135,6 +141,10 @@ public final class EmacsService extends Service
been created yet. */
private EmacsSafThread storageThread;
+ /* The Thread object representing the Android user interface
+ thread. */
+ private Thread mainThread;
+
static
{
servicingQuery = new AtomicInteger ();
@@ -235,6 +245,7 @@ public final class EmacsService extends Service
/ metrics.density)
* pixelDensityX);
resolver = getContentResolver ();
+ mainThread = Thread.currentThread ();
/* If the density used to compute the text size is lesser than
160, there's likely a bug with display density computation.
@@ -383,7 +394,13 @@ public final class EmacsService extends Service
{
if (DEBUG_THREADS)
{
- if (Thread.currentThread () instanceof EmacsThread)
+ /* When SERVICE is NULL, Emacs is being executed non-interactively. */
+ if (SERVICE == null
+ /* It was previously assumed that only instances of
+ `EmacsThread' were valid for graphics calls, but this is
+ no longer true now that Lisp threads can be attached to
+ the JVM. */
+ || (Thread.currentThread () != SERVICE.mainThread))
return;
throw new RuntimeException ("Emacs thread function"
@@ -437,21 +454,6 @@ public final class EmacsService extends Service
EmacsDrawPoint.perform (drawable, gc, x, y);
}
- public void
- clearWindow (EmacsWindow window)
- {
- checkEmacsThread ();
- window.clearWindow ();
- }
-
- public void
- clearArea (EmacsWindow window, int x, int y, int width,
- int height)
- {
- checkEmacsThread ();
- window.clearArea (x, y, width, height);
- }
-
@SuppressWarnings ("deprecation")
public void
ringBell (int duration)
@@ -581,6 +583,15 @@ public final class EmacsService extends Service
return false;
}
+ public boolean
+ detectKeyboard ()
+ {
+ Configuration configuration;
+
+ configuration = getResources ().getConfiguration ();
+ return configuration.keyboard != Configuration.KEYBOARD_NOKEYS;
+ }
+
public String
nameKeysym (int keysym)
{
@@ -716,11 +727,29 @@ public final class EmacsService extends Service
restartEmacs ()
{
Intent intent;
+ PendingIntent pending;
+ AlarmManager manager;
intent = new Intent (this, EmacsActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity (intent);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ startActivity (intent);
+ else
+ {
+ /* Experimentation has established that Android 4.3 and earlier
+ versions do not attempt to recreate a process when it crashes
+ immediately after requesting that an intent for itself be
+ started. Schedule an intent to start some time after Emacs
+ exits instead. */
+
+ pending = PendingIntent.getActivity (this, 0, intent, 0);
+ manager = (AlarmManager) getSystemService (Context.ALARM_SERVICE);
+ manager.set (AlarmManager.RTC, System.currentTimeMillis () + 100,
+ pending);
+ }
+
System.exit (0);
}
@@ -905,48 +934,6 @@ public final class EmacsService extends Service
/* Content provider functions. */
- /* Return a ContentResolver capable of accessing as many files as
- possible, namely the content resolver of the last selected
- activity if available: only they posses the rights to access drag
- and drop files. */
-
- public ContentResolver
- getUsefulContentResolver ()
- {
- EmacsActivity activity;
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
- /* Since the system predates drag and drop, return this resolver
- to avoid any unforeseen difficulties. */
- return resolver;
-
- activity = EmacsActivity.lastFocusedActivity;
- if (activity == null)
- return resolver;
-
- return activity.getContentResolver ();
- }
-
- /* Return a context whose ContentResolver is granted access to most
- files, as in `getUsefulContentResolver'. */
-
- public Context
- getContentResolverContext ()
- {
- EmacsActivity activity;
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
- /* Since the system predates drag and drop, return this resolver
- to avoid any unforeseen difficulties. */
- return this;
-
- activity = EmacsActivity.lastFocusedActivity;
- if (activity == null)
- return this;
-
- return activity;
- }
-
/* Open a content URI described by the bytes BYTES, a non-terminated
string; make it writable if WRITABLE, and readable if READABLE.
Truncate the file if TRUNCATE.
@@ -960,9 +947,6 @@ public final class EmacsService extends Service
String name, mode;
ParcelFileDescriptor fd;
int i;
- ContentResolver resolver;
-
- resolver = getUsefulContentResolver ();
/* Figure out the file access mode. */
@@ -1024,12 +1008,8 @@ public final class EmacsService extends Service
ParcelFileDescriptor fd;
Uri uri;
int rc, flags;
- Context context;
- ContentResolver resolver;
ParcelFileDescriptor descriptor;
- context = getContentResolverContext ();
-
uri = Uri.parse (name);
flags = 0;
@@ -1039,7 +1019,7 @@ public final class EmacsService extends Service
if (writable)
flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
- rc = context.checkCallingUriPermission (uri, flags);
+ rc = checkCallingUriPermission (uri, flags);
if (rc == PackageManager.PERMISSION_GRANTED)
return true;
@@ -1053,7 +1033,6 @@ public final class EmacsService extends Service
try
{
- resolver = context.getContentResolver ();
descriptor = resolver.openFileDescriptor (uri, "r");
return true;
}
@@ -1077,22 +1056,114 @@ public final class EmacsService extends Service
return false;
}
+ /* Return a 8 character checksum for the string STRING, after encoding
+ as UTF-8 data. */
+
+ public static String
+ getDisplayNameHash (String string)
+ {
+ byte[] encoded;
+ ByteArrayOutputStream stream;
+ int i, ch;
+
+ /* Much of the VFS code expects file names to be encoded as modified
+ UTF-8 data, but Android's JNI implementation produces (while not
+ accepting!) regular UTF-8 sequences for all characters, even
+ non-Emoji ones. With no documentation to this effect, save for
+ two comments nestled in the source code of the Java virtual
+ machine, it is not sound to assume that this behavior will not be
+ revised in future or modified releases of Android, and as such,
+ encode STRING into modified UTF-8 by hand, to protect against
+ future changes in this respect. */
+
+ stream = new ByteArrayOutputStream ();
+
+ for (i = 0; i < string.length (); ++i)
+ {
+ ch = string.charAt (i);
+
+ if (ch != 0 && ch <= 127)
+ stream.write (ch);
+ else if (ch <= 2047)
+ {
+ stream.write (0xc0 | (0x1f & (ch >> 6)));
+ stream.write (0x80 | (0x3f & ch));
+ }
+ else
+ {
+ stream.write (0xe0 | (0x0f & (ch >> 12)));
+ stream.write (0x80 | (0x3f & (ch >> 6)));
+ stream.write (0x80 | (0x3f & ch));
+ }
+ }
+
+ encoded = stream.toByteArray ();
+
+ /* Closing a ByteArrayOutputStream has no effect.
+ encoded.close (); */
+
+ return EmacsNative.displayNameHash (encoded);
+ }
+
/* Build a content file name for URI.
Return a file name within the /contents/by-authority
pseudo-directory that `android_get_content_name' can then
transform back into an encoded URI.
+ If a display name can be requested from URI (using the resolver
+ RESOLVER), append it to this file name.
+
A content name consists of any number of unencoded path segments
separated by `/' characters, possibly followed by a question mark
and an encoded query string. */
public static String
- buildContentName (Uri uri)
+ buildContentName (Uri uri, ContentResolver resolver)
{
StringBuilder builder;
+ String displayName;
+ Cursor cursor;
+ int column;
+
+ displayName = null;
+ cursor = null;
+
+ try
+ {
+ cursor = resolver.query (uri, null, null, null, null);
+
+ if (cursor != null)
+ {
+ cursor.moveToFirst ();
+ column
+ = cursor.getColumnIndexOrThrow (OpenableColumns.DISPLAY_NAME);
+ displayName
+ = cursor.getString (column);
+
+ /* Verify that the display name is valid, i.e. it
+ contains no characters unsuitable for a file name and
+ is nonempty. */
+ if (displayName.isEmpty () || displayName.contains ("/"))
+ displayName = null;
+ }
+ }
+ catch (Exception e)
+ {
+ /* Ignored. */
+ }
+ finally
+ {
+ if (cursor != null)
+ cursor.close ();
+ }
+
+ /* If a display name is available, at this point it should be the
+ value of displayName. */
- builder = new StringBuilder ("/content/by-authority/");
+ builder = new StringBuilder (displayName != null
+ ? "/content/by-authority-named/"
+ : "/content/by-authority/");
builder.append (uri.getAuthority ());
/* First, append each path segment. */
@@ -1109,6 +1180,16 @@ public final class EmacsService extends Service
if (uri.getEncodedQuery () != null)
builder.append ('?').append (uri.getEncodedQuery ());
+ /* Append the display name. */
+
+ if (displayName != null)
+ {
+ builder.append ('/');
+ builder.append (getDisplayNameHash (displayName));
+ builder.append ('/');
+ builder.append (displayName);
+ }
+
return builder.toString ();
}
@@ -2011,4 +2092,29 @@ public final class EmacsService extends Service
else
requestStorageAccess30 ();
}
+
+
+
+ /* Notification miscellany. */
+
+ /* Cancel any notification displayed with the tag TAG. */
+
+ public void
+ cancelNotification (final String string)
+ {
+ Object tem;
+ final NotificationManager manager;
+
+ tem = getSystemService (Context.NOTIFICATION_SERVICE);
+ manager = (NotificationManager) tem;
+
+ runOnUiThread (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ manager.cancel (string, 2);
+ }
+ });
+ }
};
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java
index 136d8abc713..109208b2518 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -456,7 +456,6 @@ public final class EmacsView extends ViewGroup
{
Canvas canvas;
Rect damageRect;
- Bitmap bitmap;
/* Make sure this function is called only from the Emacs
thread. */
@@ -474,11 +473,12 @@ public final class EmacsView extends ViewGroup
damageRect = damageRegion.getBounds ();
damageRegion.setEmpty ();
- bitmap = getBitmap ();
-
- /* Transfer the bitmap to the surface view, then invalidate
- it. */
- surfaceView.setBitmap (bitmap, damageRect);
+ synchronized (this)
+ {
+ /* Transfer the bitmap to the surface view, then invalidate
+ it. */
+ surfaceView.setBitmap (bitmap, damageRect);
+ }
}
@Override
@@ -724,17 +724,20 @@ public final class EmacsView extends ViewGroup
public synchronized void
onDetachedFromWindow ()
{
- isAttachedToWindow = false;
-
- /* Recycle the bitmap and call GC. */
-
- if (bitmap != null)
- bitmap.recycle ();
+ Bitmap savedBitmap;
+ savedBitmap = bitmap;
+ isAttachedToWindow = false;
bitmap = null;
canvas = null;
+
surfaceView.setBitmap (null, null);
+ /* Recycle the bitmap and call GC. */
+
+ if (savedBitmap != null)
+ savedBitmap.recycle ();
+
/* Collect the bitmap storage; it could be large. */
Runtime.getRuntime ().gc ();
@@ -835,9 +838,13 @@ public final class EmacsView extends ViewGroup
EmacsNative.requestSelectionUpdate (window.handle);
}
- if (mode == EmacsService.IC_MODE_ACTION)
+ if (mode == EmacsService.IC_MODE_ACTION
+ || mode == EmacsService.IC_MODE_PASSWORD)
info.imeOptions |= EditorInfo.IME_ACTION_DONE;
+ if (mode == EmacsService.IC_MODE_PASSWORD)
+ info.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
+
/* Set the initial selection fields. */
info.initialSelStart = selection[0];
info.initialSelEnd = selection[1];
diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java
index 207bd22c538..2baede1d2d0 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -23,12 +23,14 @@ import java.lang.IllegalStateException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import android.app.Activity;
+
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Rect;
@@ -47,6 +49,7 @@ import android.view.View;
import android.view.ViewManager;
import android.view.WindowManager;
+import android.util.SparseArray;
import android.util.Log;
import android.os.Build;
@@ -106,7 +109,7 @@ public final class EmacsWindow extends EmacsHandleObject
/* Map between pointer identifiers and last known position. Used to
compute which pointer changed upon a touch event. */
- private HashMap<Integer, Coordinate> pointerMap;
+ private SparseArray<Coordinate> pointerMap;
/* The window consumer currently attached, if it exists. */
private EmacsWindowAttachmentManager.WindowConsumer attached;
@@ -163,7 +166,7 @@ public final class EmacsWindow extends EmacsHandleObject
super (handle);
rect = new Rect (x, y, x + width, y + height);
- pointerMap = new HashMap<Integer, Coordinate> ();
+ pointerMap = new SparseArray<Coordinate> ();
/* Create the view from the context's UI thread. The window is
unmapped, so the view is GONE. */
@@ -240,7 +243,7 @@ public final class EmacsWindow extends EmacsHandleObject
}
}
- EmacsActivity.invalidateFocus ();
+ EmacsActivity.invalidateFocus (4);
if (!children.isEmpty ())
throw new IllegalStateException ("Trying to destroy window with "
@@ -362,6 +365,9 @@ public final class EmacsWindow extends EmacsHandleObject
requestViewLayout ();
}
+ /* Return WM layout parameters for an override redirect window with
+ the geometry provided here. */
+
private WindowManager.LayoutParams
getWindowLayoutParams ()
{
@@ -384,15 +390,15 @@ public final class EmacsWindow extends EmacsHandleObject
return params;
}
- private Context
+ private Activity
findSuitableActivityContext ()
{
/* Find a recently focused activity. */
if (!EmacsActivity.focusedActivities.isEmpty ())
return EmacsActivity.focusedActivities.get (0);
- /* Return the service context, which probably won't work. */
- return EmacsService.SERVICE;
+ /* Resort to the last activity to be focused. */
+ return EmacsActivity.lastFocusedActivity;
}
public synchronized void
@@ -416,7 +422,7 @@ public final class EmacsWindow extends EmacsHandleObject
{
EmacsWindowAttachmentManager manager;
WindowManager windowManager;
- Context ctx;
+ Activity ctx;
Object tem;
WindowManager.LayoutParams params;
@@ -447,11 +453,23 @@ public final class EmacsWindow extends EmacsHandleObject
activity using the system window manager. */
ctx = findSuitableActivityContext ();
+
+ if (ctx == null)
+ {
+ Log.w (TAG, "failed to attach override-redirect window"
+ + " for want of activity");
+ return;
+ }
+
tem = ctx.getSystemService (Context.WINDOW_SERVICE);
windowManager = (WindowManager) tem;
- /* Calculate layout parameters. */
+ /* Calculate layout parameters and propagate the
+ activity's token into it. */
+
params = getWindowLayoutParams ();
+ params.token = (ctx.findViewById (android.R.id.content)
+ .getWindowToken ());
view.setLayoutParams (params);
/* Attach the view. */
@@ -644,7 +662,7 @@ public final class EmacsWindow extends EmacsHandleObject
public void
onKeyDown (int keyCode, KeyEvent event)
{
- int state, state_1, num_lock_flag;
+ int state, state_1, extra_ignored;
long serial;
String characters;
@@ -665,23 +683,37 @@ public final class EmacsWindow extends EmacsHandleObject
state = eventModifiers (event);
- /* Num Lock and Scroll Lock aren't supported by systems older than
- Android 3.0. */
+ /* Num Lock, Scroll Lock and Meta aren't supported by systems older
+ than Android 3.0. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
- num_lock_flag = (KeyEvent.META_NUM_LOCK_ON
- | KeyEvent.META_SCROLL_LOCK_ON);
+ extra_ignored = (KeyEvent.META_NUM_LOCK_ON
+ | KeyEvent.META_SCROLL_LOCK_ON
+ | KeyEvent.META_META_MASK);
else
- num_lock_flag = 0;
+ extra_ignored = 0;
/* Ignore meta-state understood by Emacs for now, or key presses
- such as Ctrl+C and Meta+C will not be recognized as an ASCII
- key press event. */
+ such as Ctrl+C and Meta+C will not be recognized as ASCII key
+ press events. */
state_1
= state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
- | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK
- | num_lock_flag);
+ | KeyEvent.META_SYM_ON | extra_ignored);
+
+ /* There's no distinction between Right Alt and Alt Gr on Android,
+ so restore META_ALT_RIGHT_ON if set in state to enable composing
+ characters. (bug#69321) */
+
+ if ((state & KeyEvent.META_ALT_RIGHT_ON) != 0)
+ {
+ state_1 |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+
+ /* If Alt is also not depressed, remove its bit from the mask
+ reported to Emacs. */
+ if ((state & KeyEvent.META_ALT_LEFT_ON) == 0)
+ state &= ~KeyEvent.META_ALT_MASK;
+ }
synchronized (eventStrings)
{
@@ -702,29 +734,43 @@ public final class EmacsWindow extends EmacsHandleObject
public void
onKeyUp (int keyCode, KeyEvent event)
{
- int state, state_1, unicode_char, num_lock_flag;
+ int state, state_1, unicode_char, extra_ignored;
long time;
/* Compute the event's modifier mask. */
state = eventModifiers (event);
- /* Num Lock and Scroll Lock aren't supported by systems older than
- Android 3.0. */
+ /* Num Lock, Scroll Lock and Meta aren't supported by systems older
+ than Android 3.0. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
- num_lock_flag = (KeyEvent.META_NUM_LOCK_ON
- | KeyEvent.META_SCROLL_LOCK_ON);
+ extra_ignored = (KeyEvent.META_NUM_LOCK_ON
+ | KeyEvent.META_SCROLL_LOCK_ON
+ | KeyEvent.META_META_MASK);
else
- num_lock_flag = 0;
+ extra_ignored = 0;
/* Ignore meta-state understood by Emacs for now, or key presses
- such as Ctrl+C and Meta+C will not be recognized as an ASCII
- key press event. */
+ such as Ctrl+C and Meta+C will not be recognized as ASCII key
+ press events. */
state_1
= state & ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK
- | KeyEvent.META_SYM_ON | KeyEvent.META_META_MASK
- | num_lock_flag);
+ | KeyEvent.META_SYM_ON | extra_ignored);
+
+ /* There's no distinction between Right Alt and Alt Gr on Android,
+ so restore META_ALT_RIGHT_ON if set in state to enable composing
+ characters. */
+
+ if ((state & KeyEvent.META_ALT_RIGHT_ON) != 0)
+ {
+ state_1 |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+
+ /* If Alt is also not depressed, remove its bit from the mask
+ reported to Emacs. */
+ if ((state & KeyEvent.META_ALT_LEFT_ON) == 0)
+ state &= ~KeyEvent.META_ALT_MASK;
+ }
unicode_char = getEventUnicodeChar (event, state_1);
@@ -760,7 +806,7 @@ public final class EmacsWindow extends EmacsHandleObject
public void
onFocusChanged (boolean gainFocus)
{
- EmacsActivity.invalidateFocus ();
+ EmacsActivity.invalidateFocus (gainFocus ? 6 : 5);
}
/* Notice that the activity has been detached or destroyed.
@@ -955,7 +1001,8 @@ public final class EmacsWindow extends EmacsHandleObject
case MotionEvent.ACTION_CANCEL:
/* Primary pointer released with index 0. */
pointerID = event.getPointerId (0);
- coordinate = pointerMap.remove (pointerID);
+ coordinate = pointerMap.get (pointerID);
+ pointerMap.delete (pointerID);
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -974,7 +1021,8 @@ public final class EmacsWindow extends EmacsHandleObject
/* Pointer removed. Remove it from the map. */
pointerIndex = event.getActionIndex ();
pointerID = event.getPointerId (pointerIndex);
- coordinate = pointerMap.remove (pointerID);
+ coordinate = pointerMap.get (pointerID);
+ pointerMap.delete (pointerID);
break;
default:
@@ -1654,10 +1702,11 @@ public final class EmacsWindow extends EmacsHandleObject
ClipData data;
ClipDescription description;
int i, j, x, y, itemCount;
- String type;
+ String type, uriString;
Uri uri;
EmacsActivity activity;
StringBuilder builder;
+ ContentResolver resolver;
x = (int) event.getX ();
y = (int) event.getY ();
@@ -1746,7 +1795,7 @@ public final class EmacsWindow extends EmacsHandleObject
/* Attempt to acquire permissions for this URI;
failing which, insert it as text instead. */
-
+
if (uri != null
&& uri.getScheme () != null
&& uri.getScheme ().equals ("content")
@@ -1754,6 +1803,20 @@ public final class EmacsWindow extends EmacsHandleObject
{
if ((activity.requestDragAndDropPermissions (event) == null))
uri = null;
+ else
+ {
+ resolver = activity.getContentResolver ();
+
+ /* Substitute a content file name for the URI, if
+ possible. */
+ uriString = EmacsService.buildContentName (uri, resolver);
+
+ if (uriString != null)
+ {
+ builder.append (uriString).append ("\n");
+ continue;
+ }
+ }
}
if (uri != null)
@@ -1784,4 +1847,32 @@ public final class EmacsWindow extends EmacsHandleObject
return true;
}
+
+
+
+ /* Miscellaneous functions for debugging graphics code. */
+
+ /* Recreate the activity to which this window is attached, if any.
+ This is nonfunctional on Android 2.3.7 and earlier. */
+
+ public void
+ recreateActivity ()
+ {
+ final EmacsWindowAttachmentManager.WindowConsumer attached;
+
+ attached = this.attached;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
+ return;
+
+ view.post (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ if (attached instanceof EmacsActivity)
+ ((EmacsActivity) attached).recreate ();
+ }
+ });
+ }
};
diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
index 18bdb6dbf60..aae4e2ee49b 100644
--- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
@@ -124,10 +124,15 @@ public final class EmacsWindowAttachmentManager
intent = new Intent (EmacsService.SERVICE,
EmacsMultitaskActivity.class);
- intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
- | Intent.FLAG_ACTIVITY_NEW_TASK
+
+ intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ /* Intent.FLAG_ACTIVITY_NEW_DOCUMENT is lamentably unavailable on
+ older systems than Lolipop. */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ intent.addFlags (Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
EmacsService.SERVICE.startActivity (intent);
else