From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / android / project / xscreensaver / src / org / jwz / xscreensaver / XScreenSaverDaydream.java
1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * xscreensaver, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
3  *
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 
10  * implied warranty.
11  *
12  * The superclass of every saver's Daydream.
13  *
14  * Each Daydream needs a distinct subclass in order to show up in the list.
15  * We know which saver we are running by the subclass name; we know which
16  * API to use by how the subclass calls super().
17  */
18
19 package org.jwz.xscreensaver;
20
21 import java.lang.Exception;
22 import android.view.View;
23 import android.view.Window;
24 import android.view.WindowManager;
25 import android.view.KeyEvent;
26 import android.service.dreams.DreamService;
27 import android.opengl.GLSurfaceView;
28 import android.view.GestureDetector;
29 import android.view.KeyEvent;
30 import android.view.MotionEvent;
31 import android.graphics.Bitmap;
32 import android.graphics.Canvas;
33 import android.os.Message;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.app.AlertDialog;
37 import android.content.DialogInterface;
38 import android.util.Log;
39
40 public class XScreenSaverDaydream extends DreamService
41   implements GestureDetector.OnGestureListener,
42              GestureDetector.OnDoubleTapListener,
43              Handler.Callback {
44
45   private GLSurfaceView glview;
46   private int api;
47   XScreenSaverRenderer renderer;
48   private GestureDetector detector;
49   boolean button_down_p;
50   Bitmap screenshot;
51
52   private void LOG (String fmt, Object... args) {
53     Log.d ("xscreensaver",
54            this.getClass().getSimpleName() + ": " +
55            String.format (fmt, args));
56   }
57
58   protected XScreenSaverDaydream (int api) {
59     super();
60     this.api = api;
61   }
62
63   // Called when jwxyz_abort() is called, or other exceptions are thrown.
64   //
65   public boolean handleMessage (Message msg) {
66
67     String err = msg.obj.toString();
68     LOG ("Caught exception: %s", err);
69
70     this.finish();  // Exit the Daydream
71
72     final AlertDialog.Builder b = new AlertDialog.Builder(this);
73     b.setMessage (err);
74     b.setCancelable (false);
75     b.setPositiveButton ("Bummer",
76                          new DialogInterface.OnClickListener() {
77                            public void onClick(DialogInterface d, int id) {
78                            }
79                          });
80
81     // #### This isn't working:
82     // "Attempted to add window with non-application token"
83     // "Unable to add window -- token null is not for an application"
84     // I think I need to get an "Activity" to run it on somehow?
85
86     new Handler (Looper.getMainLooper()).post (new Runnable() {
87         public void run() {
88           AlertDialog alert = b.create();
89           alert.setTitle (this.getClass().getSimpleName() + " crashed");
90           alert.setIcon(android.R.drawable.ic_dialog_alert);
91           alert.show();
92         }
93       });
94
95     return true;
96   }
97
98
99   @Override
100   public void onAttachedToWindow() {
101     super.onAttachedToWindow();
102
103     setInteractive (true);
104     setFullscreen (true);
105     saveScreenshot();
106
107     // Extract the saver name from e.g. "BouncingCowDaydream"
108     String name = this.getClass().getSimpleName();
109     int index = name.lastIndexOf('$');
110     if (index != -1) {
111       index++;
112       name = name.substring (index, name.length() - index);
113     }
114     name = name.toLowerCase();
115
116     WindowManager wm = (WindowManager) getSystemService (WINDOW_SERVICE);
117     glview = new GLSurfaceView (this);
118     renderer =
119       new XScreenSaverRenderer (name, api, getApplicationContext(), wm,
120                                 screenshot, this, glview);
121     glview.setEGLConfigChooser (8, 8, 8, 8, 16, 0);
122     glview.setRenderer (renderer);
123     glview.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);
124     setContentView (glview);
125
126     detector = new GestureDetector (this, this);
127   }
128
129   public void onDreamingStarted() {
130     super.onDreamingStarted();
131   }
132
133   public void onDreamingStopped() {
134     super.onDreamingStopped();
135     glview.onPause();
136   }
137
138   public void onDetachedFromWindow() {
139     super.onDetachedFromWindow();
140     glview.onPause();
141   }
142
143
144   // At startup, before we have blanked the screen, save a screenshot
145   // for later use by the hacks.
146   //
147   private void saveScreenshot() {
148     View view = getWindow().getDecorView().getRootView();
149     if (view == null) {
150       LOG ("unable to get root view for screenshot");
151     } else {
152
153       // This doesn't work:
154   /*
155       boolean was = view.isDrawingCacheEnabled();
156       if (!was) view.setDrawingCacheEnabled (true);
157       view.buildDrawingCache();
158       screenshot = view.getDrawingCache();
159       if (!was) view.setDrawingCacheEnabled (false);
160       if (screenshot == null) {
161         LOG ("unable to get screenshot bitmap from %s", view.toString());
162       } else {
163         screenshot = Bitmap.createBitmap (screenshot);
164       }
165    */
166
167       // This doesn't work either: width and height are both -1...
168
169       int w = view.getLayoutParams().width;
170       int h = view.getLayoutParams().height;
171       if (w <= 0 || h <= 0) {
172         LOG ("unable to get root view for screenshot");
173       } else {
174         screenshot = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
175         Canvas c = new Canvas (screenshot);
176         view.layout (0, 0, w, h);
177         view.draw (c);
178       }
179     }
180   }
181
182
183
184   /* We distinguish between taps and drags.
185
186      - Drags/pans (down, motion, up) are sent to the saver to handle.
187      - Single-taps exit the saver.
188      - Long-press single-taps are sent to the saver as ButtonPress/Release;
189      - Double-taps are sent to the saver as a "Space" keypress.
190
191      #### TODO:
192      - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow.
193    */
194
195   @Override
196   public boolean onSingleTapConfirmed (MotionEvent event) {
197     this.finish();  // Exit the Daydream
198     return true;
199   }
200
201   @Override
202   public boolean onDoubleTap (MotionEvent event) {
203     renderer.sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
204                                          KeyEvent.KEYCODE_SPACE));
205     return true;
206   }
207
208   @Override
209   public void onLongPress (MotionEvent event) {
210     if (! button_down_p) {
211       int x = (int) event.getX (event.getPointerId (0));
212       int y = (int) event.getY (event.getPointerId (0));
213       renderer.sendButtonEvent (x, y, true);
214       renderer.sendButtonEvent (x, y, false);
215     }
216   }
217
218   @Override
219   public void onShowPress (MotionEvent event) {
220     if (! button_down_p) {
221       button_down_p = true;
222       int x = (int) event.getX (event.getPointerId (0));
223       int y = (int) event.getY (event.getPointerId (0));
224       renderer.sendButtonEvent (x, y, true);
225     }
226   }
227
228   @Override
229   public boolean onScroll (MotionEvent e1, MotionEvent e2, 
230                            float distanceX, float distanceY) {
231     if (button_down_p)
232       renderer.sendMotionEvent ((int) e2.getX (e2.getPointerId (0)),
233                                 (int) e2.getY (e2.getPointerId (0)));
234     return true;
235   }
236
237   // If you drag too fast, you get a single onFling event instead of a
238   // succession of onScroll events.  I can't figure out how to disable it.
239   @Override
240   public boolean onFling (MotionEvent e1, MotionEvent e2, 
241                           float velocityX, float velocityY) {
242     return false;
243   }
244
245   public boolean dragEnded (MotionEvent event) {
246     if (button_down_p) {
247       int x = (int) event.getX (event.getPointerId (0));
248       int y = (int) event.getY (event.getPointerId (0));
249       renderer.sendButtonEvent (x, y, false);
250       button_down_p = false;
251     }
252     return true;
253   }
254
255   @Override
256   public boolean onDown (MotionEvent event) {
257     return false;
258   }
259
260   @Override
261   public boolean onSingleTapUp (MotionEvent event) {
262     return false;
263   }
264
265   @Override
266   public boolean onDoubleTapEvent (MotionEvent event) {
267     return false;
268   }
269
270   @Override 
271   public boolean dispatchTouchEvent (MotionEvent event) {
272     detector.onTouchEvent (event);
273     if (event.getAction() == MotionEvent.ACTION_UP)
274       dragEnded (event);
275     return super.dispatchTouchEvent (event);
276   }
277
278   @Override
279   public boolean dispatchKeyEvent (KeyEvent event) {
280
281     // In the emulator, this doesn't receive keyboard arrow keys, PgUp, etc.
282     // Some other keys like "Home" are interpreted before we get here, and
283     // function keys do weird shit.
284
285     super.dispatchKeyEvent (event);
286     renderer.sendKeyEvent (event);
287     return true;
288   }
289
290   // Dunno what dispatchKeyShortcutEvent does, but apparently nothing useful.
291
292 }