1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * xscreensaver, Copyright (c) 2016-2018 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
12 * This class is how the C implementation of jwxyz calls back into Java
13 * to do things that OpenGL does not have access to without Java-based APIs.
14 * It is the Java companion to jwxyz-android.c and screenhack-android.c.
17 package org.jwz.xscreensaver;
20 import java.util.HashMap;
21 import java.util.Hashtable;
22 import java.util.ArrayList;
23 import java.util.Random;
24 import android.app.AlertDialog;
25 import android.view.KeyEvent;
26 import android.content.SharedPreferences;
27 import android.content.Context;
28 import android.content.ContentResolver;
29 import android.content.DialogInterface;
30 import android.content.res.AssetManager;
31 import android.graphics.Typeface;
32 import android.graphics.Rect;
33 import android.graphics.Paint;
34 import android.graphics.Paint.FontMetrics;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.Canvas;
38 import android.graphics.Color;
39 import android.graphics.Matrix;
40 import android.net.Uri;
41 import android.view.GestureDetector;
42 import android.view.KeyEvent;
43 import android.view.MotionEvent;
45 import java.nio.ByteBuffer;
47 import java.io.InputStream;
48 import java.io.FileOutputStream;
49 import java.lang.InterruptedException;
50 import java.lang.Runnable;
51 import java.lang.Thread;
52 import java.util.TimerTask;
53 import android.database.Cursor;
54 import android.provider.MediaStore;
55 import android.provider.MediaStore.MediaColumns;
56 import android.media.ExifInterface;
57 import org.jwz.xscreensaver.TTFAnalyzer;
58 import android.util.Log;
59 import android.view.Surface;
62 implements GestureDetector.OnGestureListener,
63 GestureDetector.OnDoubleTapListener {
65 private class PrefListener
66 implements SharedPreferences.OnSharedPreferenceChangeListener {
69 public void onSharedPreferenceChanged (SharedPreferences sharedPreferences, String key)
71 if (key.startsWith(hack + "_")) {
73 boolean was_animating;
74 synchronized (render) {
75 was_animating = animating_p;
85 private static class SurfaceLost extends Exception {
87 super("surface lost");
90 SurfaceLost (String detailMessage) {
95 public final static int STYLE_BOLD = 1;
96 public final static int STYLE_ITALIC = 2;
97 public final static int STYLE_MONOSPACE = 4;
99 public final static int FONT_FAMILY = 0;
100 public final static int FONT_FACE = 1;
101 public final static int FONT_RANDOM = 2;
103 private long nativeRunningHackPtr;
107 private Bitmap screenshot;
109 SharedPreferences prefs;
110 SharedPreferences.OnSharedPreferenceChangeListener pref_listener;
111 Hashtable<String, String> defaults = new Hashtable<String, String>();
114 // Maps font names to either: String (system font) or Typeface (bundled).
115 private Hashtable<String, Object> all_fonts =
116 new Hashtable<String, Object>();
122 // Doubles as the mutex controlling width/height/animating_p.
123 private Thread render;
125 private Runnable on_quit;
126 boolean button_down_p;
128 // These are defined in jwxyz-android.c:
130 private native long nativeInit (String hack,
131 Hashtable<String,String> defaults,
132 int w, int h, Surface window)
134 private native void nativeResize (int w, int h, double rot);
135 private native long nativeRender ();
136 private native void nativeDone ();
137 public native void sendButtonEvent (int x, int y, boolean down);
138 public native void sendMotionEvent (int x, int y);
139 public native void sendKeyEvent (boolean down_p, int code, int mods);
141 private void LOG (String fmt, Object... args) {
142 Log.d ("xscreensaver", hack + ": " + String.format (fmt, args));
145 static public String saverNameOf (Object obj) {
146 // Extract the saver name from e.g. "gen.Daydream$BouncingCow"
147 String name = obj.getClass().getSimpleName();
148 int index = name.lastIndexOf('$');
151 name = name.substring (index, name.length() - index);
153 return name.toLowerCase();
157 public jwxyz (String hack, Context app, Bitmap screenshot, int w, int h,
158 Surface surface, Runnable on_quit) {
162 this.screenshot = screenshot;
163 this.on_quit = on_quit;
166 this.surface = surface;
168 // nativeInit populates 'defaults' with the default values for keys
169 // that are not overridden by SharedPreferences.
171 prefs = app.getSharedPreferences (hack, 0);
173 // Keep a strong reference to pref_listener, because
174 // registerOnSharedPreferenceChangeListener only uses a weak reference.
175 pref_listener = new PrefListener();
176 prefs.registerOnSharedPreferenceChangeListener (pref_listener);
181 protected void finalize() {
182 if (render != null) {
183 LOG ("jwxyz finalized without close. This might be OK.");
189 public String getStringResource (String name) {
191 name = hack + "_" + name;
193 if (prefs.contains(name)) {
195 // SharedPreferences is very picky that you request the exact type that
196 // was stored: if it is a float and you ask for a string, you get an
197 // exception instead of the float converted to a string.
200 try { return prefs.getString (name, "");
201 } catch (Exception e) { }
203 try { return Float.toString (prefs.getFloat (name, 0));
204 } catch (Exception e) { }
206 try { return Long.toString (prefs.getLong (name, 0));
207 } catch (Exception e) { }
209 try { return Integer.toString (prefs.getInt (name, 0));
210 } catch (Exception e) { }
212 try { return (prefs.getBoolean (name, false) ? "true" : "false");
213 } catch (Exception e) { }
216 // If we got to here, it's not in there, so return the default.
217 return defaults.get (name);
221 private String mungeFontName (String name) {
222 // Roboto-ThinItalic => RobotoThin
223 // AndroidCock Regular => AndroidClock
224 String tails[] = { "Bold", "Italic", "Oblique", "Regular" };
225 for (String tail : tails) {
226 String pres[] = { " ", "-", "_", "" };
227 for (String pre : pres) {
228 int i = name.indexOf(pre + tail);
229 if (i > 0) name = name.substring (0, i);
236 private void scanSystemFonts() {
238 // First parse the system font directories for the global fonts.
240 String[] fontdirs = { "/system/fonts", "/system/font", "/data/fonts" };
241 TTFAnalyzer analyzer = new TTFAnalyzer();
242 for (String fontdir : fontdirs) {
243 File dir = new File(fontdir);
246 File[] files = dir.listFiles();
250 for (File file : files) {
251 String name = analyzer.getTtfFontName (file.getAbsolutePath());
253 // LOG ("unparsable system font: %s", file);
255 name = mungeFontName (name);
256 if (! all_fonts.contains (name)) {
257 // LOG ("system font \"%s\" %s", name, file);
258 all_fonts.put (name, name);
264 // Now parse our assets, for our bundled fonts.
266 AssetManager am = app.getAssets();
267 String dir = "fonts";
268 String[] files = null;
269 try { files = am.list(dir); }
270 catch (Exception e) { LOG("listing assets: %s", e.toString()); }
272 for (String fn : files) {
273 String fn2 = dir + "/" + fn;
274 Typeface t = Typeface.createFromAsset (am, fn2);
278 tmpfile = new File(app.getCacheDir(), fn);
279 if (tmpfile.createNewFile() == false) {
281 tmpfile.createNewFile();
284 InputStream in = am.open (fn2);
285 FileOutputStream out = new FileOutputStream (tmpfile);
286 byte[] buffer = new byte[1024 * 512];
287 while (in.read(buffer, 0, 1024 * 512) != -1) {
293 String name = analyzer.getTtfFontName (tmpfile.getAbsolutePath());
296 name = mungeFontName (name);
297 all_fonts.put (name, t);
298 // LOG ("asset font \"%s\" %s", name, fn);
299 } catch (Exception e) {
300 if (tmpfile != null) tmpfile.delete();
301 LOG ("error: %s", e.toString());
307 // Parses family names from X Logical Font Descriptions, including a few
308 // standard X font names that aren't handled by try_xlfd_font().
309 // Returns [ String name, Typeface ]
310 private Object[] parseXLFD (int mask, int traits,
311 String name, int name_type) {
312 boolean fixed = false;
313 boolean serif = false;
315 int style_jwxyz = mask & traits;
317 if (name_type != FONT_RANDOM) {
318 if ((style_jwxyz & STYLE_BOLD) != 0 ||
319 name.equals("fixed") ||
320 name.equals("courier") ||
321 name.equals("console") ||
322 name.equals("lucidatypewriter") ||
323 name.equals("monospace")) {
325 } else if (name.equals("times") ||
326 name.equals("georgia") ||
327 name.equals("serif")) {
329 } else if (name.equals("serif-monospace")) {
334 Random r = new Random();
335 serif = r.nextBoolean(); // Not much to randomize here...
336 fixed = (r.nextInt(8) == 0);
340 ? (serif ? "serif-monospace" : "monospace")
341 : (serif ? "serif" : "sans-serif"));
343 int style_android = 0;
344 if ((style_jwxyz & STYLE_BOLD) != 0)
345 style_android |= Typeface.BOLD;
346 if ((style_jwxyz & STYLE_ITALIC) != 0)
347 style_android |= Typeface.ITALIC;
349 return new Object[] { name, Typeface.create(name, style_android) };
353 // Parses "Native Font Name One 12, Native Font Name Two 14".
354 // Returns [ String name, Typeface ]
355 private Object[] parseNativeFont (String name) {
356 Object font2 = all_fonts.get (name);
357 if (font2 instanceof String)
358 font2 = Typeface.create (name, Typeface.NORMAL);
359 return new Object[] { name, (Typeface)font2 };
363 // Returns [ Paint paint, String family_name, Float ascent, Float descent ]
364 public Object[] loadFont(int mask, int traits, String name, int name_type,
368 if (name_type != FONT_RANDOM && name.equals("")) return null;
370 if (name_type == FONT_FACE) {
371 pair = parseNativeFont (name);
373 pair = parseXLFD (mask, traits, name, name_type);
376 String name2 = (String) pair[0];
377 Typeface font = (Typeface) pair[1];
381 String suffix = (font.isBold() && font.isItalic() ? " bold italic" :
382 font.isBold() ? " bold" :
383 font.isItalic() ? " italic" :
385 Paint paint = new Paint();
386 paint.setTypeface (font);
387 paint.setTextSize (size);
388 paint.setColor (Color.argb (0xFF, 0xFF, 0xFF, 0xFF));
390 LOG ("load font \"%s\" = \"%s %.1f\"", name, name2 + suffix, size);
392 FontMetrics fm = paint.getFontMetrics();
393 return new Object[] { paint, name2, -fm.ascent, fm.descent };
397 /* Returns a byte[] array containing XCharStruct with an optional
398 bitmap appended to it.
399 lbearing, rbearing, width, ascent, descent: 2 bytes each.
400 Followed by a WxH pixmap, 32 bits per pixel.
402 public ByteBuffer renderText (Paint paint, String text, boolean render_p,
403 boolean antialias_p) {
410 /* Font metric terminology, as used by X11:
412 "lbearing" is the distance from the logical origin to the leftmost
413 pixel. If a character's ink extends to the left of the origin, it is
416 "rbearing" is the distance from the logical origin to the rightmost
419 "descent" is the distance from the logical origin to the bottommost
420 pixel. For characters with descenders, it is positive. For
421 superscripts, it is negative.
423 "ascent" is the distance from the logical origin to the topmost pixel.
424 It is the number of pixels above the baseline.
426 "width" is the distance from the logical origin to the position where
427 the logical origin of the next character should be placed.
429 If "rbearing" is greater than "width", then this character overlaps the
430 following character. If smaller, then there is trailing blank space.
432 The bbox coordinates returned by getTextBounds grow down and right:
433 for a character with ink both above and below the baseline, top is
434 negative and bottom is positive.
436 paint.setAntiAlias (antialias_p);
437 FontMetrics fm = paint.getFontMetrics();
438 Rect bbox = new Rect();
439 paint.getTextBounds (text, 0, text.length(), bbox);
441 /* The bbox returned by getTextBounds measures from the logical origin
442 with right and down being positive. This means most characters have
443 a negative top, and characters with descenders have a positive bottom.
445 int lbearing = bbox.left;
446 int rbearing = bbox.right;
447 int ascent = -bbox.top;
448 int descent = bbox.bottom;
449 int width = (int) paint.measureText (text);
451 int w = rbearing - lbearing;
452 int h = ascent + descent;
453 int size = 5 * 2 + (render_p ? w * h * 4 : 0);
455 ByteBuffer bits = ByteBuffer.allocateDirect (size);
457 bits.put ((byte) ((lbearing >> 8) & 0xFF));
458 bits.put ((byte) ( lbearing & 0xFF));
459 bits.put ((byte) ((rbearing >> 8) & 0xFF));
460 bits.put ((byte) ( rbearing & 0xFF));
461 bits.put ((byte) ((width >> 8) & 0xFF));
462 bits.put ((byte) ( width & 0xFF));
463 bits.put ((byte) ((ascent >> 8) & 0xFF));
464 bits.put ((byte) ( ascent & 0xFF));
465 bits.put ((byte) ((descent >> 8) & 0xFF));
466 bits.put ((byte) ( descent & 0xFF));
468 if (render_p && w > 0 && h > 0) {
469 Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
470 Canvas canvas = new Canvas (bitmap);
471 canvas.drawText (text, -lbearing, ascent, paint);
472 bitmap.copyPixelsToBuffer (bits);
480 /* Returns the contents of the URL.
481 Loads the URL in a background thread: if the URL has not yet loaded,
482 this will return null. Once the URL has completely loaded, the full
483 contents will be returned. Calling this again after that starts the
486 private String loading_url = null;
487 private ByteBuffer loaded_url_body = null;
489 public synchronized ByteBuffer loadURL (String url) {
491 if (loaded_url_body != null) { // Thread finished
493 // LOG ("textclient finished %s", loading_url);
495 ByteBuffer bb = loaded_url_body;
497 loaded_url_body = null;
500 } else if (loading_url != null) { // Waiting on thread
501 // LOG ("textclient waiting...");
504 } else { // Launch thread
507 LOG ("textclient launching %s...", url);
509 new Thread (new Runnable() {
514 ByteBuffer body = ByteBuffer.allocateDirect (size);
517 URL u = new URL (loading_url);
518 // LOG ("textclient thread loading: %s", u.toString());
519 InputStream s = u.openStream();
520 byte buf[] = new byte[10240];
522 int n = s.read (buf);
524 // LOG ("textclient thread read %d", n);
525 if (count + n + 1 >= size) {
526 int size2 = (int) (size * 1.2 + size0);
527 // LOG ("textclient thread expand %d -> %d", size, size2);
528 ByteBuffer body2 = ByteBuffer.allocateDirect (size2);
531 body2.position (count);
535 body.put (buf, 0, n);
538 } catch (Exception e) {
539 LOG ("load URL error: %s", e.toString());
541 body.put (e.toString().getBytes());
545 // LOG ("textclient thread finished %s (%d)", loading_url, size);
546 loaded_url_body = body;
555 // Returns [ Bitmap bitmap, String name ]
556 private Object[] convertBitmap (String name, Bitmap bitmap,
557 int target_width, int target_height,
558 ExifInterface exif, boolean rotate_p) {
559 if (bitmap == null) return null;
563 int width = bitmap.getWidth();
564 int height = bitmap.getHeight();
565 Matrix matrix = new Matrix();
567 LOG ("read image %s: %d x %d", name, width, height);
569 // First rotate the image as per EXIF.
573 switch (exif.getAttributeInt (ExifInterface.TAG_ORIENTATION,
574 ExifInterface.ORIENTATION_NORMAL)) {
575 case ExifInterface.ORIENTATION_ROTATE_90: deg = 90; break;
576 case ExifInterface.ORIENTATION_ROTATE_180: deg = 180; break;
577 case ExifInterface.ORIENTATION_ROTATE_270: deg = 270; break;
580 LOG ("%s: EXIF rotate %d", name, deg);
581 matrix.preRotate (deg);
582 if (deg == 90 || deg == 270) {
590 // If the caller requested that we rotate the image to best fit the
591 // screen, rotate it again.
594 (width > height) != (target_width > target_height)) {
595 LOG ("%s: rotated to fit screen", name);
596 matrix.preRotate (90);
603 // Resize the image to be not larger than the screen, potentially
604 // copying it for the third time.
605 // Actually, always scale it, scaling up if necessary.
607 // if (width > target_width || height > target_height)
609 float r1 = target_width / (float) width;
610 float r2 = target_height / (float) height;
611 float r = (r1 > r2 ? r2 : r1);
612 LOG ("%s: resize %.1f: %d x %d => %d x %d", name,
613 r, width, height, (int) (width * r), (int) (height * r));
614 matrix.preScale (r, r);
617 bitmap = Bitmap.createBitmap (bitmap, 0, 0,
618 bitmap.getWidth(), bitmap.getHeight(),
621 if (bitmap.getConfig() != Bitmap.Config.ARGB_8888)
622 bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
624 return new Object[] { bitmap, name };
630 public Object[] loadRandomImage (int target_width, int target_height,
634 int max_size = 0x7FFF;
636 ArrayList<String> imgs = new ArrayList<String>();
638 ContentResolver cr = app.getContentResolver();
639 String[] cols = { MediaColumns.DATA,
640 MediaColumns.MIME_TYPE,
642 MediaColumns.HEIGHT };
644 android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI,
645 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
647 for (int i = 0; i < uris.length; i++) {
648 Cursor cursor = cr.query (uris[i], cols, null, null, null);
652 int path_col = cursor.getColumnIndexOrThrow (cols[j++]);
653 int type_col = cursor.getColumnIndexOrThrow (cols[j++]);
654 int width_col = cursor.getColumnIndexOrThrow (cols[j++]);
655 int height_col = cursor.getColumnIndexOrThrow (cols[j++]);
656 while (cursor.moveToNext()) {
657 String path = cursor.getString(path_col);
658 String type = cursor.getString(type_col);
659 if (path != null && type != null && type.startsWith("image/")) {
660 String wc = cursor.getString(width_col);
661 String hc = cursor.getString(height_col);
662 if (wc != null && hc != null) {
663 int w = Integer.parseInt (wc);
664 int h = Integer.parseInt (hc);
665 if (w > min_size && h > min_size &&
666 w < max_size && h < max_size) {
677 int count = imgs.size();
683 int i = new Random().nextInt (count);
684 which = imgs.get (i);
685 LOG ("picked image %d of %d: %s", i, count, which);
687 Uri uri = Uri.fromFile (new File (which));
688 String name = uri.getLastPathSegment();
689 Bitmap bitmap = null;
690 ExifInterface exif = null;
694 bitmap = MediaStore.Images.Media.getBitmap (cr, uri);
695 } catch (Exception e) {
696 LOG ("image %s unloadable: %s", which, e.toString());
701 exif = new ExifInterface (uri.getPath()); // If it fails, who cares
702 } catch (Exception e) {
705 return convertBitmap (name, bitmap, target_width, target_height,
707 } catch (java.lang.OutOfMemoryError e) {
708 LOG ("image %s got OutOfMemoryError: %s", which, e.toString());
714 public Object[] getScreenshot (int target_width, int target_height,
716 return convertBitmap ("Screenshot", screenshot,
717 target_width, target_height,
722 public Bitmap decodePNG (byte[] data) {
723 BitmapFactory.Options opts = new BitmapFactory.Options();
724 opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
725 return BitmapFactory.decodeByteArray (data, 0, data.length, opts);
729 // Sadly duplicated from jwxyz.h (and thence X.h and keysymdef.h)
731 private static final int ShiftMask = (1<<0);
732 private static final int LockMask = (1<<1);
733 private static final int ControlMask = (1<<2);
734 private static final int Mod1Mask = (1<<3);
735 private static final int Mod2Mask = (1<<4);
736 private static final int Mod3Mask = (1<<5);
737 private static final int Mod4Mask = (1<<6);
738 private static final int Mod5Mask = (1<<7);
739 private static final int Button1Mask = (1<<8);
740 private static final int Button2Mask = (1<<9);
741 private static final int Button3Mask = (1<<10);
742 private static final int Button4Mask = (1<<11);
743 private static final int Button5Mask = (1<<12);
745 private static final int XK_Shift_L = 0xFFE1;
746 private static final int XK_Shift_R = 0xFFE2;
747 private static final int XK_Control_L = 0xFFE3;
748 private static final int XK_Control_R = 0xFFE4;
749 private static final int XK_Caps_Lock = 0xFFE5;
750 private static final int XK_Shift_Lock = 0xFFE6;
751 private static final int XK_Meta_L = 0xFFE7;
752 private static final int XK_Meta_R = 0xFFE8;
753 private static final int XK_Alt_L = 0xFFE9;
754 private static final int XK_Alt_R = 0xFFEA;
755 private static final int XK_Super_L = 0xFFEB;
756 private static final int XK_Super_R = 0xFFEC;
757 private static final int XK_Hyper_L = 0xFFED;
758 private static final int XK_Hyper_R = 0xFFEE;
760 private static final int XK_Home = 0xFF50;
761 private static final int XK_Left = 0xFF51;
762 private static final int XK_Up = 0xFF52;
763 private static final int XK_Right = 0xFF53;
764 private static final int XK_Down = 0xFF54;
765 private static final int XK_Prior = 0xFF55;
766 private static final int XK_Page_Up = 0xFF55;
767 private static final int XK_Next = 0xFF56;
768 private static final int XK_Page_Down = 0xFF56;
769 private static final int XK_End = 0xFF57;
770 private static final int XK_Begin = 0xFF58;
772 private static final int XK_F1 = 0xFFBE;
773 private static final int XK_F2 = 0xFFBF;
774 private static final int XK_F3 = 0xFFC0;
775 private static final int XK_F4 = 0xFFC1;
776 private static final int XK_F5 = 0xFFC2;
777 private static final int XK_F6 = 0xFFC3;
778 private static final int XK_F7 = 0xFFC4;
779 private static final int XK_F8 = 0xFFC5;
780 private static final int XK_F9 = 0xFFC6;
781 private static final int XK_F10 = 0xFFC7;
782 private static final int XK_F11 = 0xFFC8;
783 private static final int XK_F12 = 0xFFC9;
785 public void sendKeyEvent (KeyEvent event) {
786 int uc = event.getUnicodeChar();
787 int jcode = event.getKeyCode();
788 int jmods = event.getModifiers();
793 case KeyEvent.KEYCODE_SHIFT_LEFT: xcode = XK_Shift_L; break;
794 case KeyEvent.KEYCODE_SHIFT_RIGHT: xcode = XK_Shift_R; break;
795 case KeyEvent.KEYCODE_CTRL_LEFT: xcode = XK_Control_L; break;
796 case KeyEvent.KEYCODE_CTRL_RIGHT: xcode = XK_Control_R; break;
797 case KeyEvent.KEYCODE_CAPS_LOCK: xcode = XK_Caps_Lock; break;
798 case KeyEvent.KEYCODE_META_LEFT: xcode = XK_Meta_L; break;
799 case KeyEvent.KEYCODE_META_RIGHT: xcode = XK_Meta_R; break;
800 case KeyEvent.KEYCODE_ALT_LEFT: xcode = XK_Alt_L; break;
801 case KeyEvent.KEYCODE_ALT_RIGHT: xcode = XK_Alt_R; break;
803 case KeyEvent.KEYCODE_HOME: xcode = XK_Home; break;
804 case KeyEvent.KEYCODE_DPAD_LEFT: xcode = XK_Left; break;
805 case KeyEvent.KEYCODE_DPAD_UP: xcode = XK_Up; break;
806 case KeyEvent.KEYCODE_DPAD_RIGHT: xcode = XK_Right; break;
807 case KeyEvent.KEYCODE_DPAD_DOWN: xcode = XK_Down; break;
808 //case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS: xcode = XK_Prior; break;
809 case KeyEvent.KEYCODE_PAGE_UP: xcode = XK_Page_Up; break;
810 //case KeyEvent.KEYCODE_NAVIGATE_NEXT: xcode = XK_Next; break;
811 case KeyEvent.KEYCODE_PAGE_DOWN: xcode = XK_Page_Down; break;
812 case KeyEvent.KEYCODE_MOVE_END: xcode = XK_End; break;
813 case KeyEvent.KEYCODE_MOVE_HOME: xcode = XK_Begin; break;
815 case KeyEvent.KEYCODE_F1: xcode = XK_F1; break;
816 case KeyEvent.KEYCODE_F2: xcode = XK_F2; break;
817 case KeyEvent.KEYCODE_F3: xcode = XK_F3; break;
818 case KeyEvent.KEYCODE_F4: xcode = XK_F4; break;
819 case KeyEvent.KEYCODE_F5: xcode = XK_F5; break;
820 case KeyEvent.KEYCODE_F6: xcode = XK_F6; break;
821 case KeyEvent.KEYCODE_F7: xcode = XK_F7; break;
822 case KeyEvent.KEYCODE_F8: xcode = XK_F8; break;
823 case KeyEvent.KEYCODE_F9: xcode = XK_F9; break;
824 case KeyEvent.KEYCODE_F10: xcode = XK_F10; break;
825 case KeyEvent.KEYCODE_F11: xcode = XK_F11; break;
826 case KeyEvent.KEYCODE_F12: xcode = XK_F12; break;
827 default: xcode = uc; break;
830 if (0 != (jmods & KeyEvent.META_SHIFT_ON)) xmods |= ShiftMask;
831 if (0 != (jmods & KeyEvent.META_CAPS_LOCK_ON)) xmods |= LockMask;
832 if (0 != (jmods & KeyEvent.META_CTRL_MASK)) xmods |= ControlMask;
833 if (0 != (jmods & KeyEvent.META_ALT_MASK)) xmods |= Mod1Mask;
834 if (0 != (jmods & KeyEvent.META_META_ON)) xmods |= Mod1Mask;
835 if (0 != (jmods & KeyEvent.META_SYM_ON)) xmods |= Mod2Mask;
836 if (0 != (jmods & KeyEvent.META_FUNCTION_ON)) xmods |= Mod3Mask;
838 /* If you touch and release Shift, you get no events.
839 If you type Shift-A, you get Shift down, A down, A up, Shift up.
840 So let's just ignore all lone modifier key events.
842 if (xcode >= XK_Shift_L && xcode <= XK_Hyper_R)
845 boolean down_p = event.getAction() == KeyEvent.ACTION_DOWN;
846 sendKeyEvent (down_p, xcode, xmods);
850 if (render == null) {
852 render = new Thread(new Runnable() {
856 int currentWidth, currentHeight;
857 synchronized (render) {
859 while (!animating_p || width == 0 || height == 0) {
862 } catch(InterruptedException exc) {
868 nativeInit (hack, defaults, width, height, surface);
869 currentWidth = width;
870 currentHeight= height;
872 } catch (SurfaceLost exc) {
881 synchronized (render) {
884 while (!animating_p) {
887 } catch(InterruptedException exc) {
892 if (currentWidth != width || currentHeight != height) {
893 currentWidth = width;
894 currentHeight = height;
895 nativeResize (width, height, 0);
899 long delay = nativeRender();
901 synchronized (render) {
904 render.wait(delay / 1000, (int)(delay % 1000) * 1000);
905 } catch (InterruptedException exc) {
909 if (Thread.interrupted ()) {
916 assert nativeRunningHackPtr != 0;
923 synchronized(render) {
933 synchronized (render) {
942 synchronized (render) {
948 } catch (InterruptedException exc) {
953 void resize (int w, int h) {
956 if (render != null) {
957 synchronized (render) {
969 /* We distinguish between taps and drags.
971 - Drags/pans (down, motion, up) are sent to the saver to handle.
972 - Single-taps exit the saver.
973 - Long-press single-taps are sent to the saver as ButtonPress/Release;
974 - Double-taps are sent to the saver as a "Space" keypress.
977 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow.
981 public boolean onSingleTapConfirmed (MotionEvent event) {
988 public boolean onDoubleTap (MotionEvent event) {
989 sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
990 KeyEvent.KEYCODE_SPACE));
995 public void onLongPress (MotionEvent event) {
996 if (! button_down_p) {
997 int x = (int) event.getX (event.getPointerId (0));
998 int y = (int) event.getY (event.getPointerId (0));
999 sendButtonEvent (x, y, true);
1000 sendButtonEvent (x, y, false);
1005 public void onShowPress (MotionEvent event) {
1006 if (! button_down_p) {
1007 button_down_p = true;
1008 int x = (int) event.getX (event.getPointerId (0));
1009 int y = (int) event.getY (event.getPointerId (0));
1010 sendButtonEvent (x, y, true);
1015 public boolean onScroll (MotionEvent e1, MotionEvent e2,
1016 float distanceX, float distanceY) {
1017 // LOG ("onScroll: %d", button_down_p ? 1 : 0);
1019 sendMotionEvent ((int) e2.getX (e2.getPointerId (0)),
1020 (int) e2.getY (e2.getPointerId (0)));
1024 // If you drag too fast, you get a single onFling event instead of a
1025 // succession of onScroll events. I can't figure out how to disable it.
1027 public boolean onFling (MotionEvent e1, MotionEvent e2,
1028 float velocityX, float velocityY) {
1032 public boolean dragEnded (MotionEvent event) {
1033 if (button_down_p) {
1034 int x = (int) event.getX (event.getPointerId (0));
1035 int y = (int) event.getY (event.getPointerId (0));
1036 sendButtonEvent (x, y, false);
1037 button_down_p = false;
1043 public boolean onDown (MotionEvent event) {
1048 public boolean onSingleTapUp (MotionEvent event) {
1053 public boolean onDoubleTapEvent (MotionEvent event) {
1059 System.loadLibrary ("xscreensaver");
1062 Thread.setDefaultUncaughtExceptionHandler(
1063 new Thread.UncaughtExceptionHandler() {
1064 Thread.UncaughtExceptionHandler old_handler =
1065 Thread.currentThread().getUncaughtExceptionHandler();
1068 public void uncaughtException (Thread thread, Throwable ex) {
1069 String err = ex.toString();
1070 Log.d ("xscreensaver", "Caught exception: " + err);
1071 old_handler.uncaughtException (thread, ex);