content: // This file is part of Golly.
  // See docs/License.html for the copyright notice.
  
  package net.sf.golly;
  
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.util.ArrayDeque;
  import java.util.Deque;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.app.Application;
  import android.content.DialogInterface;
  import android.content.res.AssetManager;
  import android.os.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.util.DisplayMetrics;
  import android.util.Log;
  
  public class BaseApp extends Application {
  	
      // see jnicalls.cpp for these native routines:
  	private static native void nativeClassInit();          // this MUST be static
  	private native void nativeCreate();                    // the rest must NOT be static
      private native void nativeSetUserDirs(String path);
      private native void nativeSetSuppliedDirs(String prefix);
      private native void nativeSetTempDir(String path);
      private native void nativeSetScreenDensity(int dpi);
      private native void nativeSetWideScreen(boolean widescreen);
  	
      public File userdir;        // directory for user-created data
      public File supplieddir;    // directory for supplied data
  
      // keep track of the current foreground activity
      private Deque<Activity> activityStack = new ArrayDeque<Activity>();
  
      // -----------------------------------------------------------------------------
      
      static {
      	System.loadLibrary("c++_shared");   // must agree with build.gradle
          System.loadLibrary("golly");        // loads libgolly.so
          nativeClassInit();                  // caches Java method IDs
      }
      
      // -----------------------------------------------------------------------------
      
      @Override
      public void onCreate() {
          super.onCreate();
          
          DisplayMetrics metrics = getResources().getDisplayMetrics();
          nativeSetScreenDensity(metrics.densityDpi);
          // Log.i("Golly","screen density in dpi = " + Integer.toString(metrics.densityDpi));
          // eg. densityDpi = 320 on Nexus 7
          
          DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
          float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
          
          // Log.i("Golly","screen width in dp = " + Integer.toString(config.screenWidthDp));
          // eg. on Nexus 7 screenWidthDp = 600 in portrait, 960 in landscape
          boolean widescreen = dpWidth >= 600;
          nativeSetWideScreen(widescreen);
          
          initPaths();        // sets userdir, supplieddir, etc (must be BEFORE nativeCreate)
          nativeCreate();     // cache this instance and initialize lots of Golly stuff
  
          this.registerActivityLifecycleCallbacks(new MyActivityLifecycleCallbacks());
      }
      
      // -----------------------------------------------------------------------------
  
      private class MyActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {
  
          @Override
          public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
  
          @Override
          public void onActivityDestroyed(Activity activity) {}
  
          @Override
          public void onActivityPaused(Activity activity) {
              // MainActivity is special (singleTask) and can be called into with NewIntent
              if (activity instanceof MainActivity) {
                  return;
              }
              activityStack.removeFirst();
          }
  
          @Override
          public void onActivityResumed(Activity activity) {
              activityStack.addFirst(activity);
          }
  
          @Override
          public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
  
          @Override
          public void onActivityStarted(Activity activity) {}
  
          @Override
          public void onActivityStopped(Activity activity) {}
      }
  
      public Activity getCurrentActivity() {
          return activityStack.peekFirst();
      }
      
      // -----------------------------------------------------------------------------
  
      private void initPaths() {
          // check if external storage is available
          String state = Environment.getExternalStorageState();
          if (Environment.MEDIA_MOUNTED.equals(state)) {
              // use external storage for user's files
              userdir = getExternalFilesDir(null);        // /mnt/sdcard/Android/data/net.sf.golly/files
          } else {
              // use internal storage for user's files
              userdir = getFilesDir();                    // /data/data/net.sf.golly/files
              Log.i("Golly", "External storage is not available, so internal storage will be used.");
          }
          
          // create subdirs in userdir (if they don't exist)
          File subdir;
          subdir = new File(userdir, "Rules");            // for user's .rule files
          subdir.mkdir();
          subdir = new File(userdir, "Saved");            // for saved pattern files
          subdir.mkdir();
          subdir = new File(userdir, "Downloads");        // for downloaded files
          subdir.mkdir();
          
          // set appropriate paths used by C++ code
          nativeSetUserDirs(userdir.getAbsolutePath());
          
          // create a directory in internal storage for supplied Patterns/Rules/Help then
          // create sub-directories for each by unzipping .zip files stored in assets
          supplieddir = new File(getFilesDir(), "Supplied");
          supplieddir.mkdir();
          unzipAsset("Patterns.zip", supplieddir);
          unzipAsset("Rules.zip", supplieddir);
          unzipAsset("Help.zip", supplieddir);
          
          // supplieddir = /data/data/net.sf.golly/files/Supplied
          nativeSetSuppliedDirs(supplieddir.getAbsolutePath());
          
          // set directory path for temporary files
          File tempdir = getCacheDir();
          nativeSetTempDir(tempdir.getAbsolutePath());    // /data/data/net.sf.golly/cache
      }
      
      // -----------------------------------------------------------------------------
  
      private void unzipAsset(String zipname, File destdir) {
          AssetManager am = getAssets();
          try {
              ZipInputStream zipstream = new ZipInputStream(am.open(zipname));
              for (ZipEntry entry = zipstream.getNextEntry(); entry != null; entry = zipstream.getNextEntry()) {
                  File destfile = new File(destdir, entry.getName());
                  if (entry.isDirectory()) {
                      // create any missing sub-directories
                      destfile.mkdirs();
                  } else {
                      // create a file
                      final int BUFFSIZE = 8192;
                      BufferedOutputStream buffstream = new BufferedOutputStream(new FileOutputStream(destfile), BUFFSIZE);
                      int count = 0;
                      byte[] data = new byte[BUFFSIZE];
                      while ((count = zipstream.read(data, 0, BUFFSIZE)) != -1) {
                          buffstream.write(data, 0, count);
                      }
                      buffstream.flush();
                      buffstream.close();
                  }
                  zipstream.closeEntry();
              }
              zipstream.close();
          } catch (Exception e) {
              Log.e("Golly", "Failed to unzip asset: " + zipname + "\nException: ", e);
              Warning("You probably forgot to put " + zipname + " in the assets folder!");
          }
      }
      
      // -----------------------------------------------------------------------------
  
      // handler for implementing modal dialogs
      static class LooperInterrupter extends Handler {
  		public void handleMessage(Message msg) {
              throw new RuntimeException();
          }
  	}
      
      // -----------------------------------------------------------------------------
      
      // this method is called from C++ code (see jnicalls.cpp)
      void Warning(String msg) {
          Activity currentActivity = getCurrentActivity();
          if (currentActivity == null) {
              Log.i("Warning", "currentActivity is null!");
          }
  
          // note that MainActivity might not be the current foreground activity
          AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
          alert.setTitle("Warning");
          alert.setMessage(msg);
          alert.setNegativeButton("CANCEL",
              new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                      dialog.cancel();
                  }
              });
          alert.show();
      }
      
      // -----------------------------------------------------------------------------
  
      // this method is called from C++ code (see jnicalls.cpp)
      void Fatal(String msg) {
          Activity currentActivity = getCurrentActivity();
          // note that MainActivity might not be the current foreground activity
          AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
          alert.setTitle("Fatal error!");
          alert.setMessage(msg);
          alert.setNegativeButton("QUIT",
              new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                      dialog.cancel();
                  }
              });
          alert.show();
  
          System.exit(1);
      }
      
      // -----------------------------------------------------------------------------
  
      private String answer;
      
      // this method is called from C++ code (see jnicalls.cpp)
      String YesNo(String query) {
          // use a handler to get a modal dialog
          // (this must be modal because it is used in an if test)
          final Handler handler = new LooperInterrupter();
  
          Activity currentActivity = getCurrentActivity();
          // note that MainActivity might not be the current foreground activity
          AlertDialog.Builder alert = new AlertDialog.Builder(currentActivity);
          alert.setTitle("A question...");
          alert.setMessage(query);
          alert.setPositiveButton("YES",
              new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                      answer = "yes";
                      dialog.cancel();
                      handler.sendMessage(handler.obtainMessage());
                  }
              });
          alert.setNegativeButton("NO",
              new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                      answer = "no";
                      dialog.cancel();
                      handler.sendMessage(handler.obtainMessage());
                  }
              });
          alert.show();
          
          // loop until runtime exception is triggered
          try { Looper.loop(); } catch(RuntimeException re) {}
  
          return answer;
      }
      
  } // BaseApp class
  