1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * xscreensaver, Copyright (c) 2016 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 * The superclass of every saver's Daydream.
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().
19 package org.jwz.xscreensaver;
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;
40 public class XScreenSaverDaydream extends DreamService
41 implements GestureDetector.OnGestureListener,
42 GestureDetector.OnDoubleTapListener,
45 private GLSurfaceView glview;
47 XScreenSaverRenderer renderer;
48 private GestureDetector detector;
49 boolean button_down_p;
52 private void LOG (String fmt, Object... args) {
53 Log.d ("xscreensaver",
54 this.getClass().getSimpleName() + ": " +
55 String.format (fmt, args));
58 protected XScreenSaverDaydream (int api) {
63 // Called when jwxyz_abort() is called, or other exceptions are thrown.
65 public boolean handleMessage (Message msg) {
67 String err = msg.obj.toString();
68 LOG ("Caught exception: %s", err);
70 this.finish(); // Exit the Daydream
72 final AlertDialog.Builder b = new AlertDialog.Builder(this);
74 b.setCancelable (false);
75 b.setPositiveButton ("Bummer",
76 new DialogInterface.OnClickListener() {
77 public void onClick(DialogInterface d, int id) {
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?
86 new Handler (Looper.getMainLooper()).post (new Runnable() {
88 AlertDialog alert = b.create();
89 alert.setTitle (this.getClass().getSimpleName() + " crashed");
90 alert.setIcon(android.R.drawable.ic_dialog_alert);
100 public void onAttachedToWindow() {
101 super.onAttachedToWindow();
103 setInteractive (true);
104 setFullscreen (true);
107 // Extract the saver name from e.g. "BouncingCowDaydream"
108 String name = this.getClass().getSimpleName();
109 int index = name.lastIndexOf('$');
112 name = name.substring (index, name.length() - index);
114 name = name.toLowerCase();
116 WindowManager wm = (WindowManager) getSystemService (WINDOW_SERVICE);
117 glview = new GLSurfaceView (this);
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);
126 detector = new GestureDetector (this, this);
129 public void onDreamingStarted() {
130 super.onDreamingStarted();
133 public void onDreamingStopped() {
134 super.onDreamingStopped();
138 public void onDetachedFromWindow() {
139 super.onDetachedFromWindow();
144 // At startup, before we have blanked the screen, save a screenshot
145 // for later use by the hacks.
147 private void saveScreenshot() {
148 View view = getWindow().getDecorView().getRootView();
150 LOG ("unable to get root view for screenshot");
153 // This doesn't work:
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());
163 screenshot = Bitmap.createBitmap (screenshot);
167 // This doesn't work either: width and height are both -1...
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");
174 screenshot = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
175 Canvas c = new Canvas (screenshot);
176 view.layout (0, 0, w, h);
184 /* We distinguish between taps and drags.
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.
192 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow.
196 public boolean onSingleTapConfirmed (MotionEvent event) {
197 this.finish(); // Exit the Daydream
202 public boolean onDoubleTap (MotionEvent event) {
203 renderer.sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
204 KeyEvent.KEYCODE_SPACE));
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);
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);
229 public boolean onScroll (MotionEvent e1, MotionEvent e2,
230 float distanceX, float distanceY) {
232 renderer.sendMotionEvent ((int) e2.getX (e2.getPointerId (0)),
233 (int) e2.getY (e2.getPointerId (0)));
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.
240 public boolean onFling (MotionEvent e1, MotionEvent e2,
241 float velocityX, float velocityY) {
245 public boolean dragEnded (MotionEvent event) {
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;
256 public boolean onDown (MotionEvent event) {
261 public boolean onSingleTapUp (MotionEvent event) {
266 public boolean onDoubleTapEvent (MotionEvent event) {
271 public boolean dispatchTouchEvent (MotionEvent event) {
272 detector.onTouchEvent (event);
273 if (event.getAction() == MotionEvent.ACTION_UP)
275 return super.dispatchTouchEvent (event);
279 public boolean dispatchKeyEvent (KeyEvent event) {
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.
285 super.dispatchKeyEvent (event);
286 renderer.sendKeyEvent (event);
290 // Dunno what dispatchKeyShortcutEvent does, but apparently nothing useful.