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.File; 7 import java.util.Arrays; 8 9 import android.app.Activity; 10 import android.app.AlertDialog; 11 import android.content.Context; 12 import android.content.DialogInterface; 13 import android.content.Intent; 14 import android.os.Bundle; 15 import android.os.Handler; 16 import android.support.v4.app.NavUtils; 17 import android.util.DisplayMetrics; 18 import android.util.Log; 19 import android.view.Menu; 20 import android.view.MenuItem; 21 import android.view.View; 22 import android.webkit.WebSettings.LayoutAlgorithm; 23 import android.webkit.WebView; 24 import android.webkit.WebViewClient; 25 import android.widget.Toast; 26 27 public class OpenActivity extends Activity { 28 29 // see jnicalls.cpp for these native routines: nativeGetRecentPatterns()30 private native String nativeGetRecentPatterns(); nativeGetSavedPatterns(String paths)31 private native String nativeGetSavedPatterns(String paths); nativeGetDownloadedPatterns(String paths)32 private native String nativeGetDownloadedPatterns(String paths); nativeGetSuppliedPatterns(String paths)33 private native String nativeGetSuppliedPatterns(String paths); nativeToggleDir(String path)34 private native void nativeToggleDir(String path); 35 36 private enum PATTERNS { 37 SUPPLIED, RECENT, SAVED, DOWNLOADED; 38 } 39 40 private static PATTERNS currpatterns = PATTERNS.SUPPLIED; 41 42 // remember scroll positions for each type of patterns 43 private static int supplied_pos = 0; 44 private static int recent_pos = 0; 45 private static int saved_pos = 0; 46 private static int downloaded_pos = 0; 47 48 private WebView gwebview; // for displaying html data 49 50 // ----------------------------------------------------------------------------- 51 52 // this class lets us intercept link taps and restore the scroll position 53 private class MyWebViewClient extends WebViewClient { 54 @Override shouldOverrideUrlLoading(WebView webview, String url)55 public boolean shouldOverrideUrlLoading(WebView webview, String url) { 56 if (url.startsWith("open:")) { 57 openFile(url.substring(5)); 58 return true; 59 } 60 if (url.startsWith("toggledir:")) { 61 nativeToggleDir(url.substring(10)); 62 saveScrollPosition(); 63 showSuppliedPatterns(); 64 return true; 65 } 66 if (url.startsWith("delete:")) { 67 removeFile(url.substring(7)); 68 return true; 69 } 70 if (url.startsWith("edit:")) { 71 editFile(url.substring(5)); 72 return true; 73 } 74 return false; 75 } 76 77 @Override onPageFinished(WebView webview, String url)78 public void onPageFinished(WebView webview, String url) { 79 super.onPageFinished(webview, url); 80 // webview.scrollTo doesn't always work here; 81 // we need to delay until webview.getContentHeight() > 0 82 final int scrollpos = restoreScrollPosition(); 83 if (scrollpos > 0) { 84 final Handler handler = new Handler(); 85 Runnable runnable = new Runnable() { 86 public void run() { 87 if (gwebview.getContentHeight() > 0) { 88 gwebview.scrollTo(0, scrollpos); 89 } else { 90 // try again a bit later 91 handler.postDelayed(this, 100); 92 } 93 } 94 }; 95 handler.postDelayed(runnable, 100); 96 } 97 98 /* following also works if we setJavaScriptEnabled(true), but is not quite as nice 99 when a folder is closed because the scroll position can change to force the 100 last line to appear at the bottom of the webview 101 int scrollpos = restoreScrollPosition(); 102 if (scrollpos > 0) { 103 final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, "); 104 sb.append(scrollpos); 105 sb.append("/ window.devicePixelRatio);"); 106 webview.loadUrl(sb.toString()); 107 } 108 super.onPageFinished(webview, url); 109 */ 110 } 111 } 112 113 // ----------------------------------------------------------------------------- 114 saveScrollPosition()115 private void saveScrollPosition() { 116 switch (currpatterns) { 117 case SUPPLIED: supplied_pos = gwebview.getScrollY(); break; 118 case RECENT: recent_pos = gwebview.getScrollY(); break; 119 case SAVED: saved_pos = gwebview.getScrollY(); break; 120 case DOWNLOADED: downloaded_pos = gwebview.getScrollY(); break; 121 } 122 } 123 124 // ----------------------------------------------------------------------------- 125 restoreScrollPosition()126 private int restoreScrollPosition() { 127 switch (currpatterns) { 128 case SUPPLIED: return supplied_pos; 129 case RECENT: return recent_pos; 130 case SAVED: return saved_pos; 131 case DOWNLOADED: return downloaded_pos; 132 } 133 return 0; // should never get here 134 } 135 136 // ----------------------------------------------------------------------------- 137 openFile(String filepath)138 private void openFile(String filepath) { 139 // switch to main screen and open given file 140 Intent intent = new Intent(this, MainActivity.class); 141 intent.putExtra(MainActivity.OPENFILE_MESSAGE, filepath); 142 startActivity(intent); 143 } 144 145 // ----------------------------------------------------------------------------- 146 removeFile(String filepath)147 private void removeFile(String filepath) { 148 BaseApp baseapp = (BaseApp)getApplicationContext(); 149 final String fullpath = baseapp.userdir.getAbsolutePath() + "/" + filepath; 150 final File file = new File(fullpath); 151 152 // ask user if it's okay to delete given file 153 AlertDialog.Builder alert = new AlertDialog.Builder(this); 154 alert.setTitle("Delete file?"); 155 alert.setMessage("Do you really want to delete " + file.getName() + "?"); 156 alert.setPositiveButton("DELETE", 157 new DialogInterface.OnClickListener() { 158 public void onClick(DialogInterface dialog, int id) { 159 if (file.delete()) { 160 // file has been deleted so refresh gwebview 161 saveScrollPosition(); 162 switch (currpatterns) { 163 case SUPPLIED: break; // should never happen 164 case RECENT: break; // should never happen 165 case SAVED: showSavedPatterns(); break; 166 case DOWNLOADED: showDownloadedPatterns(); break; 167 } 168 } else { 169 // should never happen 170 Log.e("removeFile", "Failed to delete file: " + fullpath); 171 } 172 } 173 }); 174 alert.setNegativeButton("CANCEL", null); 175 alert.show(); 176 } 177 178 // ----------------------------------------------------------------------------- 179 editFile(String filepath)180 private void editFile(String filepath) { 181 // check if filepath is compressed 182 if (filepath.endsWith(".gz") || filepath.endsWith(".zip")) { 183 if (currpatterns == PATTERNS.SUPPLIED) { 184 Toast.makeText(this, "Reading a compressed file is not supported.", Toast.LENGTH_SHORT).show(); 185 } else { 186 Toast.makeText(this, "Editing a compressed file is not supported.", Toast.LENGTH_SHORT).show(); 187 } 188 return; 189 } 190 191 BaseApp baseapp = (BaseApp)getApplicationContext(); 192 // let user read/edit given file 193 if (currpatterns == PATTERNS.SUPPLIED) { 194 // read contents of supplied file into a string 195 String fullpath = baseapp.supplieddir.getAbsolutePath() + "/" + filepath; 196 // display filecontents 197 Intent intent = new Intent(this, InfoActivity.class); 198 intent.putExtra(InfoActivity.INFO_MESSAGE, fullpath); 199 startActivity(intent); 200 } else { 201 // let user read or edit a saved or downloaded file 202 String fullpath = baseapp.userdir.getAbsolutePath() + "/" + filepath; 203 Intent intent = new Intent(this, EditActivity.class); 204 intent.putExtra(EditActivity.EDITFILE_MESSAGE, fullpath); 205 startActivity(intent); 206 } 207 } 208 209 // ----------------------------------------------------------------------------- 210 211 @Override onCreate(Bundle savedInstanceState)212 protected void onCreate(Bundle savedInstanceState) { 213 super.onCreate(savedInstanceState); 214 setContentView(R.layout.open_layout); 215 216 gwebview = (WebView) findViewById(R.id.webview); 217 gwebview.setWebViewClient(new MyWebViewClient()); 218 219 // avoid wrapping long lines -- this doesn't work: 220 // gwebview.getSettings().setUseWideViewPort(true); 221 // this is better: 222 gwebview.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); 223 224 DisplayMetrics metrics = getResources().getDisplayMetrics(); 225 if (metrics.densityDpi > 300) { 226 // use bigger font size for high density screens (default size is 16) 227 gwebview.getSettings().setDefaultFontSize(32); 228 } 229 230 // no need for JavaScript??? 231 // gwebview.getSettings().setJavaScriptEnabled(true); 232 233 // allow zooming??? 234 // gwebview.getSettings().setBuiltInZoomControls(true); 235 236 // show the Up button in the action bar 237 getActionBar().setDisplayHomeAsUpEnabled(true); 238 239 // note that onResume will do the initial display 240 } 241 242 // ----------------------------------------------------------------------------- 243 244 @Override onPause()245 protected void onPause() { 246 super.onPause(); 247 saveScrollPosition(); 248 } 249 250 // ----------------------------------------------------------------------------- 251 252 @Override onResume()253 protected void onResume() { 254 super.onResume(); 255 // update gwebview 256 restoreScrollPosition(); 257 switch (currpatterns) { 258 case SUPPLIED: showSuppliedPatterns(); break; 259 case RECENT: showRecentPatterns(); break; 260 case SAVED: showSavedPatterns(); break; 261 case DOWNLOADED: showDownloadedPatterns(); break; 262 } 263 } 264 265 // ----------------------------------------------------------------------------- 266 267 @Override onCreateOptionsMenu(Menu menu)268 public boolean onCreateOptionsMenu(Menu menu) { 269 // add main.xml items to the action bar 270 getMenuInflater().inflate(R.menu.main, menu); 271 272 // disable the item for this activity 273 MenuItem item = menu.findItem(R.id.open); 274 item.setEnabled(false); 275 276 return true; 277 } 278 279 // ----------------------------------------------------------------------------- 280 281 @Override onOptionsItemSelected(MenuItem item)282 public boolean onOptionsItemSelected(MenuItem item) { 283 // action bar item has been tapped 284 Intent intent; 285 switch (item.getItemId()) { 286 case android.R.id.home: 287 // the Home or Up button will go back to MainActivity 288 NavUtils.navigateUpFromSameTask(this); 289 return true; 290 case R.id.open: 291 // do nothing 292 break; 293 case R.id.settings: 294 finish(); 295 intent = new Intent(this, SettingsActivity.class); 296 startActivity(intent); 297 return true; 298 case R.id.help: 299 finish(); 300 intent = new Intent(this, HelpActivity.class); 301 startActivity(intent); 302 return true; 303 } 304 return super.onOptionsItemSelected(item); 305 } 306 307 // ----------------------------------------------------------------------------- 308 309 // called when the Supplied button is tapped doSupplied(View view)310 public void doSupplied(View view) { 311 if (currpatterns != PATTERNS.SUPPLIED) { 312 saveScrollPosition(); 313 currpatterns = PATTERNS.SUPPLIED; 314 showSuppliedPatterns(); 315 } 316 } 317 318 // ----------------------------------------------------------------------------- 319 320 // called when the Recent button is tapped doRecent(View view)321 public void doRecent(View view) { 322 if (currpatterns != PATTERNS.RECENT) { 323 saveScrollPosition(); 324 currpatterns = PATTERNS.RECENT; 325 showRecentPatterns(); 326 } 327 } 328 329 // ----------------------------------------------------------------------------- 330 331 // called when the Saved button is tapped doSaved(View view)332 public void doSaved(View view) { 333 if (currpatterns != PATTERNS.SAVED) { 334 saveScrollPosition(); 335 currpatterns = PATTERNS.SAVED; 336 showSavedPatterns(); 337 } 338 } 339 340 // ----------------------------------------------------------------------------- 341 342 // called when the Downloaded button is tapped doDownloaded(View view)343 public void doDownloaded(View view) { 344 if (currpatterns != PATTERNS.DOWNLOADED) { 345 saveScrollPosition(); 346 currpatterns = PATTERNS.DOWNLOADED; 347 showDownloadedPatterns(); 348 } 349 } 350 351 // ----------------------------------------------------------------------------- 352 enumerateDirectory(File dir, String prefix)353 private String enumerateDirectory(File dir, String prefix) { 354 // return the files and/or sub-directories in the given directory 355 // as a string of paths where: 356 // - paths are relative to to the initial directory 357 // - directory paths end with '/' 358 // - each path is terminated by \n 359 String result = ""; 360 File[] files = dir.listFiles(); 361 if (files != null) { 362 Arrays.sort(files); // sort into alphabetical order 363 for (File file : files) { 364 if (file.isDirectory()) { 365 String dirname = prefix + file.getName() + "/"; 366 result += dirname + "\n"; 367 result += enumerateDirectory(file, dirname); 368 } else { 369 result += prefix + file.getName() + "\n"; 370 } 371 } 372 } 373 return result; 374 } 375 376 // ----------------------------------------------------------------------------- 377 showSuppliedPatterns()378 private void showSuppliedPatterns() { 379 BaseApp baseapp = (BaseApp)getApplicationContext(); 380 String paths = enumerateDirectory(new File(baseapp.supplieddir, "Patterns"), ""); 381 String htmldata = nativeGetSuppliedPatterns(paths); 382 // use a special base URL so that <img src="foo.png"/> will extract foo.png from the assets folder 383 gwebview.loadDataWithBaseURL("file:///android_asset/", htmldata, "text/html", "utf-8", null); 384 } 385 386 // ----------------------------------------------------------------------------- 387 showRecentPatterns()388 private void showRecentPatterns() { 389 String htmldata = nativeGetRecentPatterns(); 390 gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null); 391 } 392 393 // ----------------------------------------------------------------------------- 394 showSavedPatterns()395 private void showSavedPatterns() { 396 BaseApp baseapp = (BaseApp)getApplicationContext(); 397 String paths = enumerateDirectory(new File(baseapp.userdir, "Saved"), ""); 398 String htmldata = nativeGetSavedPatterns(paths); 399 gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null); 400 } 401 402 // ----------------------------------------------------------------------------- 403 showDownloadedPatterns()404 private void showDownloadedPatterns() { 405 BaseApp baseapp = (BaseApp)getApplicationContext(); 406 String paths = enumerateDirectory(new File(baseapp.userdir, "Downloads"), ""); 407 String htmldata = nativeGetDownloadedPatterns(paths); 408 gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null); 409 } 410 411 } // OpenActivity class 412