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