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