1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 package net.sf.golly;
5 
6 import java.io.BufferedOutputStream;
7 import java.io.File;
8 import java.io.FileOutputStream;
9 import java.util.ArrayDeque;
10 import java.util.Deque;
11 import java.util.zip.ZipEntry;
12 import java.util.zip.ZipInputStream;
13 
14 import android.app.Activity;
15 import android.app.AlertDialog;
16 import android.app.Application;
17 import android.content.DialogInterface;
18 import android.content.res.AssetManager;
19 import android.os.Bundle;
20 import android.os.Environment;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.util.DisplayMetrics;
25 import android.util.Log;
26 
27 public class BaseApp extends Application {
28 
29     // see jnicalls.cpp for these native routines:
nativeClassInit()30 	private static native void nativeClassInit();          // this MUST be static
nativeCreate()31 	private native void nativeCreate();                    // the rest must NOT be static
nativeSetUserDirs(String path)32     private native void nativeSetUserDirs(String path);
nativeSetSuppliedDirs(String prefix)33     private native void nativeSetSuppliedDirs(String prefix);
nativeSetTempDir(String path)34     private native void nativeSetTempDir(String path);
nativeSetScreenDensity(int dpi)35     private native void nativeSetScreenDensity(int dpi);
nativeSetWideScreen(boolean widescreen)36     private native void nativeSetWideScreen(boolean widescreen);
37 
38     public File userdir;        // directory for user-created data
39     public File supplieddir;    // directory for supplied data
40 
41     // keep track of the current foreground activity
42     private Deque<Activity> activityStack = new ArrayDeque<Activity>();
43 
44     // -----------------------------------------------------------------------------
45 
46     static {
47     	System.loadLibrary("stlport_shared");   // must agree with build.gradle
48         System.loadLibrary("golly");            // loads libgolly.so
nativeClassInit()49         nativeClassInit();                      // caches Java method IDs
50     }
51 
52     // -----------------------------------------------------------------------------
53 
54     @Override
onCreate()55     public void onCreate() {
56         super.onCreate();
57 
58         DisplayMetrics metrics = getResources().getDisplayMetrics();
59         nativeSetScreenDensity(metrics.densityDpi);
60         // Log.i("Golly","screen density in dpi = " + Integer.toString(metrics.densityDpi));
61         // eg. densityDpi = 320 on Nexus 7
62 
63         DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
64         float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
65 
66         // Log.i("Golly","screen width in dp = " + Integer.toString(config.screenWidthDp));
67         // eg. on Nexus 7 screenWidthDp = 600 in portrait, 960 in landscape
68         boolean widescreen = dpWidth >= 600;
69         nativeSetWideScreen(widescreen);
70 
71         initPaths();        // sets userdir, supplieddir, etc (must be BEFORE nativeCreate)
72         nativeCreate();     // cache this instance and initialize lots of Golly stuff
73 
74         this.registerActivityLifecycleCallbacks(new MyActivityLifecycleCallbacks());
75     }
76 
77     // -----------------------------------------------------------------------------
78 
79     private class MyActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {
80 
81         @Override
onActivityCreated(Activity activity, Bundle savedInstanceState)82         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
83 
84         @Override
onActivityDestroyed(Activity activity)85         public void onActivityDestroyed(Activity activity) {}
86 
87         @Override
onActivityPaused(Activity activity)88         public void onActivityPaused(Activity activity) {
89             // MainActivity is special (singleTask) and can be called into with NewIntent
90             if (activity instanceof MainActivity) {
91                 return;
92             }
93             activityStack.removeFirst();
94         }
95 
96         @Override
onActivityResumed(Activity activity)97         public void onActivityResumed(Activity activity) {
98             activityStack.addFirst(activity);
99         }
100 
101         @Override
onActivitySaveInstanceState(Activity activity, Bundle outState)102         public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
103 
104         @Override
onActivityStarted(Activity activity)105         public void onActivityStarted(Activity activity) {}
106 
107         @Override
onActivityStopped(Activity activity)108         public void onActivityStopped(Activity activity) {}
109     }
110 
getCurrentActivity()111     public Activity getCurrentActivity() {
112         return activityStack.peekFirst();
113     }
114 
115     // -----------------------------------------------------------------------------
116 
initPaths()117     private void initPaths() {
118         // check if external storage is available
119         String state = Environment.getExternalStorageState();
120         if (Environment.MEDIA_MOUNTED.equals(state)) {
121             // use external storage for user's files
122             userdir = getExternalFilesDir(null);        // /mnt/sdcard/Android/data/net.sf.golly/files
123         } else {
124             // use internal storage for user's files
125             userdir = getFilesDir();                    // /data/data/net.sf.golly/files
126             Log.i("Golly", "External storage is not available, so internal storage will be used.");
127         }
128 
129         // create subdirs in userdir (if they don't exist)
130         File subdir;
131         subdir = new File(userdir, "Rules");            // for user's .rule files
132         subdir.mkdir();
133         subdir = new File(userdir, "Saved");            // for saved pattern files
134         subdir.mkdir();
135         subdir = new File(userdir, "Downloads");        // for downloaded files
136         subdir.mkdir();
137 
138         // set appropriate paths used by C++ code
139         nativeSetUserDirs(userdir.getAbsolutePath());
140 
141         // create a directory in internal storage for supplied Patterns/Rules/Help then
142         // create sub-directories for each by unzipping .zip files stored in assets
143         supplieddir = new File(getFilesDir(), "Supplied");
144         supplieddir.mkdir();
145         unzipAsset("Patterns.zip", supplieddir);
146         unzipAsset("Rules.zip", supplieddir);
147         unzipAsset("Help.zip", supplieddir);
148 
149         // supplieddir = /data/data/net.sf.golly/files/Supplied
150         nativeSetSuppliedDirs(supplieddir.getAbsolutePath());
151 
152         // set directory path for temporary files
153         File tempdir = getCacheDir();
154         nativeSetTempDir(tempdir.getAbsolutePath());    // /data/data/net.sf.golly/cache
155     }
156 
157     // -----------------------------------------------------------------------------
158 
unzipAsset(String zipname, File destdir)159     private void unzipAsset(String zipname, File destdir) {
160         AssetManager am = getAssets();
161         try {
162             ZipInputStream zipstream = new ZipInputStream(am.open(zipname));
163             for (ZipEntry entry = zipstream.getNextEntry(); entry != null; entry = zipstream.getNextEntry()) {
164                 File destfile = new File(destdir, entry.getName());
165                 if (entry.isDirectory()) {
166                     // create any missing sub-directories
167                     destfile.mkdirs();
168                 } else {
169                     // create a file
170                     final int BUFFSIZE = 8192;
171                     BufferedOutputStream buffstream = new BufferedOutputStream(new FileOutputStream(destfile), BUFFSIZE);
172                     int count = 0;
173                     byte[] data = new byte[BUFFSIZE];
174                     while ((count = zipstream.read(data, 0, BUFFSIZE)) != -1) {
175                         buffstream.write(data, 0, count);
176                     }
177                     buffstream.flush();
178                     buffstream.close();
179                 }
180                 zipstream.closeEntry();
181             }
182             zipstream.close();
183         } catch (Exception e) {
184             Log.e("Golly", "Failed to unzip asset: " + zipname + "\nException: ", e);
185             Warning("You probably forgot to put " + zipname + " in the assets folder!");
186         }
187     }
188 
189     // -----------------------------------------------------------------------------
190 
191     // handler for implementing modal dialogs
192     static class LooperInterrupter extends Handler {
handleMessage(Message msg)193 		public void handleMessage(Message msg) {
194             throw new RuntimeException();
195         }
196 	}
197 
198     // -----------------------------------------------------------------------------
199 
200     // this method is called from C++ code (see jnicalls.cpp)
Warning(String msg)201     void Warning(String msg) {
202         Activity currentActivity = getCurrentActivity();
203         if (currentActivity == null) {
204             Log.i("Warning", "currentActivity is null!");
205         }
206 
207         // note that MainActivity might not be the current foreground activity
208         AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
209         alert.setTitle("Warning");
210         alert.setMessage(msg);
211         alert.setNegativeButton("CANCEL",
212             new DialogInterface.OnClickListener() {
213                 public void onClick(DialogInterface dialog, int id) {
214                     dialog.cancel();
215                 }
216             });
217         alert.show();
218     }
219 
220     // -----------------------------------------------------------------------------
221 
222     // this method is called from C++ code (see jnicalls.cpp)
Fatal(String msg)223     void Fatal(String msg) {
224         Activity currentActivity = getCurrentActivity();
225         // note that MainActivity might not be the current foreground activity
226         AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
227         alert.setTitle("Fatal error!");
228         alert.setMessage(msg);
229         alert.setNegativeButton("QUIT",
230             new DialogInterface.OnClickListener() {
231                 public void onClick(DialogInterface dialog, int id) {
232                     dialog.cancel();
233                 }
234             });
235         alert.show();
236 
237         System.exit(1);
238     }
239 
240     // -----------------------------------------------------------------------------
241 
242     private String answer;
243 
244     // this method is called from C++ code (see jnicalls.cpp)
YesNo(String query)245     String YesNo(String query) {
246         // use a handler to get a modal dialog
247         // (this must be modal because it is used in an if test)
248         final Handler handler = new LooperInterrupter();
249 
250         Activity currentActivity = getCurrentActivity();
251         // note that MainActivity might not be the current foreground activity
252         AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
253         alert.setTitle("A question...");
254         alert.setMessage(query);
255         alert.setPositiveButton("YES",
256             new DialogInterface.OnClickListener() {
257                 public void onClick(DialogInterface dialog, int id) {
258                     answer = "yes";
259                     dialog.cancel();
260                     handler.sendMessage(handler.obtainMessage());
261                 }
262             });
263         alert.setNegativeButton("NO",
264             new DialogInterface.OnClickListener() {
265                 public void onClick(DialogInterface dialog, int id) {
266                     answer = "no";
267                     dialog.cancel();
268                     handler.sendMessage(handler.obtainMessage());
269                 }
270             });
271         alert.show();
272 
273         // loop until runtime exception is triggered
274         try { Looper.loop(); } catch(RuntimeException re) {}
275 
276         return answer;
277     }
278 
279 } // BaseApp class
280