From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / android / xscreensaver / src / org / jwz / xscreensaver / XScreenSaverDaydream.java
1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * xscreensaver, Copyright (c) 2016-2017 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 android.view.Display;
22 import android.view.Surface;
23 import android.view.SurfaceHolder;
24 import android.view.SurfaceView;
25 import android.view.View;
26 import android.view.Window;
27 import android.view.WindowManager;
28 import android.view.KeyEvent;
29 import android.service.dreams.DreamService;
30 import android.view.GestureDetector;
31 import android.view.KeyEvent;
32 import android.view.MotionEvent;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.os.Message;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.util.Log;
39
40 public class XScreenSaverDaydream extends DreamService {
41
42   private class SaverView extends SurfaceView
43     implements SurfaceHolder.Callback {
44
45     private boolean initTried = false;
46     private jwxyz jwxyz_obj;
47
48     private GestureDetector detector;
49
50     private Runnable on_quit = new Runnable() {
51       @Override
52       public void run() {
53         finish();  // Exit the Daydream
54       }
55     };
56
57     SaverView () {
58       super (XScreenSaverDaydream.this);
59       getHolder().addCallback(this);
60     }
61
62     @Override
63     public void surfaceChanged (SurfaceHolder holder, int format,
64                                 int width, int height) {
65
66       if (width == 0 || height == 0) {
67         detector = null;
68         jwxyz_obj.close();
69         jwxyz_obj = null;
70       }
71
72       Log.d ("xscreensaver",
73              String.format("surfaceChanged: %dx%d", width, height));
74
75       /*
76       double r = 0;
77
78       Display d = view.getDisplay();
79
80       if (d != null) {
81         switch (d.getRotation()) {
82         case Surface.ROTATION_90:  r = 90;  break;
83         case Surface.ROTATION_180: r = 180; break;
84         case Surface.ROTATION_270: r = 270; break;
85         }
86       }
87       */
88
89       if (jwxyz_obj == null) {
90         jwxyz_obj = new jwxyz (jwxyz.saverNameOf (XScreenSaverDaydream.this),
91                                XScreenSaverDaydream.this, screenshot,
92                                width, height, holder.getSurface(), on_quit);
93         detector = new GestureDetector (XScreenSaverDaydream.this, jwxyz_obj);
94       } else {
95         jwxyz_obj.resize (width, height);
96       }
97
98       jwxyz_obj.start();
99     }
100
101     @Override
102     public void surfaceCreated (SurfaceHolder holder) {
103       if (!initTried) {
104         initTried = true;
105       } else {
106         if (jwxyz_obj != null) {
107           jwxyz_obj.close();
108           jwxyz_obj = null;
109         }
110       }
111     }
112
113     @Override
114     public void surfaceDestroyed (SurfaceHolder holder) {
115       if (jwxyz_obj != null) {
116         jwxyz_obj.close();
117         jwxyz_obj = null;
118       }
119     }
120
121     @Override
122     public boolean onTouchEvent (MotionEvent event) {
123       detector.onTouchEvent (event);
124       if (event.getAction() == MotionEvent.ACTION_UP)
125         jwxyz_obj.dragEnded (event);
126       return true;
127     }
128
129     @Override
130     public boolean onKeyDown (int keyCode, KeyEvent event) {
131       // In the emulator, this doesn't receive keyboard arrow keys, PgUp, etc.
132       // Some other keys like "Home" are interpreted before we get here, and
133       // function keys do weird shit.
134
135       // TODO: Does this still work? And is the above still true?
136
137       if (view.jwxyz_obj != null)
138         view.jwxyz_obj.sendKeyEvent (event);
139       return true;
140     }
141   }
142
143   private SaverView view;
144   Bitmap screenshot;
145
146   private void LOG (String fmt, Object... args) {
147     Log.d ("xscreensaver",
148            this.getClass().getSimpleName() + ": " +
149            String.format (fmt, args));
150   }
151
152   protected XScreenSaverDaydream () {
153     super();
154   }
155
156   // Called when jwxyz_abort() is called, or other exceptions are thrown.
157   //
158 /*
159   @Override
160   public void uncaughtException (Thread thread, Throwable ex) {
161
162     renderer = null;
163     String err = ex.toString();
164     LOG ("Caught exception: %s", err);
165
166     this.finish();  // Exit the Daydream
167
168     final AlertDialog.Builder b = new AlertDialog.Builder(this);
169     b.setMessage (err);
170     b.setCancelable (false);
171     b.setPositiveButton ("Bummer",
172                          new DialogInterface.OnClickListener() {
173                            public void onClick(DialogInterface d, int id) {
174                            }
175                          });
176
177     // #### This isn't working:
178     // "Attempted to add window with non-application token"
179     // "Unable to add window -- token null is not for an application"
180     // I think I need to get an "Activity" to run it on somehow?
181
182     new Handler (Looper.getMainLooper()).post (new Runnable() {
183         public void run() {
184           AlertDialog alert = b.create();
185           alert.setTitle (this.getClass().getSimpleName() + " crashed");
186           alert.setIcon(android.R.drawable.ic_dialog_alert);
187           alert.show();
188         }
189       });
190
191     old_handler.uncaughtException (thread, ex);
192   }
193 */
194
195
196   @Override
197   public void onAttachedToWindow() {
198     super.onAttachedToWindow();
199
200     setInteractive (true);
201     setFullscreen (true);
202     saveScreenshot();
203
204     view = new SaverView ();
205     setContentView (view);
206   }
207
208   public void onDreamingStarted() {
209     super.onDreamingStarted();
210     // view.jwxyz_obj.start();
211   }
212
213   public void onDreamingStopped() {
214     super.onDreamingStopped();
215     view.jwxyz_obj.pause();
216   }
217
218   public void onDetachedFromWindow() {
219     super.onDetachedFromWindow();
220     try {
221       if (view.jwxyz_obj != null)
222         view.jwxyz_obj.pause();
223     } catch (Exception exc) {
224       // Fun fact: Android swallows exceptions coming from here, then crashes
225       // elsewhere.
226       LOG ("onDetachedFromWindow: %s", exc.toString());
227       throw exc;
228     }
229   }
230
231
232   // At startup, before we have blanked the screen, save a screenshot
233   // for later use by the hacks.
234   //
235   private void saveScreenshot() {
236     View view = getWindow().getDecorView().getRootView();
237     if (view == null) {
238       LOG ("unable to get root view for screenshot");
239     } else {
240
241       // This doesn't work:
242   /*
243       boolean was = view.isDrawingCacheEnabled();
244       if (!was) view.setDrawingCacheEnabled (true);
245       view.buildDrawingCache();
246       screenshot = view.getDrawingCache();
247       if (!was) view.setDrawingCacheEnabled (false);
248       if (screenshot == null) {
249         LOG ("unable to get screenshot bitmap from %s", view.toString());
250       } else {
251         screenshot = Bitmap.createBitmap (screenshot);
252       }
253    */
254
255       // This doesn't work either: width and height are both -1...
256
257       int w = view.getLayoutParams().width;
258       int h = view.getLayoutParams().height;
259       if (w <= 0 || h <= 0) {
260         LOG ("unable to get root view for screenshot");
261       } else {
262         screenshot = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
263         Canvas c = new Canvas (screenshot);
264         view.layout (0, 0, w, h);
265         view.draw (c);
266       }
267     }
268   }
269 }