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 String tail = "Daydream";
110 if (name.endsWith(tail))
111 name = name.substring (0, name.length() - tail.length());
112 name = name.toLowerCase();
114 WindowManager wm = (WindowManager) getSystemService (WINDOW_SERVICE);
116 new XScreenSaverRenderer (name, api, getApplicationContext(), wm,
119 glview = new GLSurfaceView (this);
120 glview.setRenderer (renderer);
121 setContentView (glview);
123 detector = new GestureDetector (this, this);
126 public void onDreamingStarted() {
127 super.onDreamingStarted();
130 public void onDreamingStopped() {
131 super.onDreamingStopped();
135 public void onDetachedFromWindow() {
136 super.onDetachedFromWindow();
141 // At startup, before we have blanked the screen, save a screenshot
142 // for later use by the hacks.
144 private void saveScreenshot() {
145 View view = getWindow().getDecorView().getRootView();
147 LOG ("unable to get root view for screenshot");
150 // This doesn't work:
152 boolean was = view.isDrawingCacheEnabled();
153 if (!was) view.setDrawingCacheEnabled (true);
154 view.buildDrawingCache();
155 screenshot = view.getDrawingCache();
156 if (!was) view.setDrawingCacheEnabled (false);
157 if (screenshot == null) {
158 LOG ("unable to get screenshot bitmap from %s", view.toString());
160 screenshot = Bitmap.createBitmap (screenshot);
164 // This doesn't work either: width and height are both -1...
166 int w = view.getLayoutParams().width;
167 int h = view.getLayoutParams().height;
168 if (w <= 0 || h <= 0) {
169 LOG ("unable to get root view for screenshot");
171 screenshot = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
172 Canvas c = new Canvas (screenshot);
173 view.layout (0, 0, w, h);
181 /* We distinguish between taps and drags.
183 - Drags/pans (down, motion, up) are sent to the saver to handle.
184 - Single-taps exit the saver.
185 - Long-press single-taps are sent to the saver as ButtonPress/Release;
186 - Double-taps are sent to the saver as a "Space" keypress.
189 - Swipes (really, two-finger drags/pans) send Up/Down/Left/RightArrow.
193 public boolean onSingleTapConfirmed (MotionEvent event) {
194 this.finish(); // Exit the Daydream
199 public boolean onDoubleTap (MotionEvent event) {
200 renderer.sendKeyEvent (new KeyEvent (KeyEvent.ACTION_DOWN,
201 KeyEvent.KEYCODE_SPACE));
206 public void onLongPress (MotionEvent event) {
207 if (! button_down_p) {
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 renderer.sendButtonEvent (x, y, false);
216 public void onShowPress (MotionEvent event) {
217 if (! button_down_p) {
218 button_down_p = true;
219 int x = (int) event.getX (event.getPointerId (0));
220 int y = (int) event.getY (event.getPointerId (0));
221 renderer.sendButtonEvent (x, y, true);
226 public boolean onScroll (MotionEvent e1, MotionEvent e2,
227 float distanceX, float distanceY) {
229 renderer.sendMotionEvent ((int) e2.getX (e2.getPointerId (0)),
230 (int) e2.getY (e2.getPointerId (0)));
234 // If you drag too fast, you get a single onFling event instead of a
235 // succession of onScroll events. I can't figure out how to disable it.
237 public boolean onFling (MotionEvent e1, MotionEvent e2,
238 float velocityX, float velocityY) {
242 public boolean dragEnded (MotionEvent event) {
244 int x = (int) event.getX (event.getPointerId (0));
245 int y = (int) event.getY (event.getPointerId (0));
246 renderer.sendButtonEvent (x, y, false);
247 button_down_p = false;
253 public boolean onDown (MotionEvent event) {
258 public boolean onSingleTapUp (MotionEvent event) {
263 public boolean onDoubleTapEvent (MotionEvent event) {
268 public boolean dispatchTouchEvent (MotionEvent event) {
269 detector.onTouchEvent (event);
270 if (event.getAction() == MotionEvent.ACTION_UP)
272 return super.dispatchTouchEvent (event);
276 public boolean dispatchKeyEvent (KeyEvent event) {
278 // In the emulator, this doesn't receive keyboard arrow keys, PgUp, etc.
279 // Some other keys like "Home" are interpreted before we get here, and
280 // function keys do weird shit.
282 super.dispatchKeyEvent (event);
283 renderer.sendKeyEvent (event);
287 // Dunno what dispatchKeyShortcutEvent does, but apparently nothing useful.