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