├── README.md ├── Refresh-for-Quinoid.json └── app └── src └── main ├── AndroidManifest.xml ├── assets └── TwResources.json ├── java └── com │ └── example │ └── my32ndapplication │ ├── DisplayMessageActivity.java │ ├── TwActivity.java │ ├── TwDialogFragment.java │ ├── TwFile.java │ ├── TwFragment.java │ ├── TwFragmentActivity.java │ ├── TwJSONSerializer.java │ ├── TwManager.java │ ├── TwPagerActivity.java │ ├── TwResource.java │ ├── TwResourceActivity.java │ ├── TwResourceFragment.java │ ├── TwResourceRecyclerViewAdapter.java │ ├── TwUtils.java │ └── WebAppInterface.java └── res ├── layout ├── activity_display_message.xml ├── activity_main.xml ├── fragment_twresource.xml ├── fragment_twresource_list.xml ├── list_item.xml ├── tw_fragment.xml ├── tw_fragment_activity.xml ├── tw_fragment_dialog.xml ├── tw_pager_view.xml └── tw_resource_activity.xml ├── menu └── menu_main.xml └── values ├── colors.xml ├── dimens.xml ├── ids.xml ├── strings.xml └── styles.xml /README.md: -------------------------------------------------------------------------------- 1 | # Quinoid 0.0.8alpha 2 | 3 | **Application to use TiddlyWiki on Android** 4 | 5 | After Android 4.4 (KitKat) the Android permissions and file access model changed. 6 | This meant that older Android (unnamed) applications for 7 | TiddlyWiki could not always save. 8 | This project will hopefully work better with the new permissions model. 9 | 10 | ## Features 11 | 12 | * Switch active page by swiping (working) 13 | * List active TW files in working list, using your own preferred title. 14 | * Hand off links to external websites to other browsers 15 | * Select which files should be used in browse (swipe) mode. 16 | * Display TW files' favicon, if available 17 | * Access to files provided by the Android system explorer, enabling access to services such as GDrive. (working?) 18 | * Create local files based on editions (in progess) 19 | * Configurable list of TW files that can be downloaded on demand (next?) 20 | * Auto-load files from directory (working?). 21 | * Capture text shared from other apps via Android sharing . 22 | * Import tiddlers, plugins, etc. 23 | 24 | ## Aspirational / roadmap 25 | 26 | * Serve up files? Maybe. 27 | * Light file maintenance - physically delete TW's no longer wanted. 28 | * User preferences to automatically open in browsing session 29 | 30 | ## DISCLAIMER 31 | 32 | All releases should be consider **beta** or even **alpha** quality. Be sure to make any backups before using with important TW files. Be 33 | especially careful when dealing with files on GDrive or other synchronized platforms. At the moment, Quinoid will consider itself 34 | "in charge" of any files you have listed, and could possibly write over your synchronized platform file with its own internal version. 35 | 36 | In all cases, it is up to you to properly backup and secure your files. 37 | 38 | ## Usage Notes 39 | 40 | **Intallation note:** 41 | On some versions of Android, you may need to go into the application manager and grant yourself storage permissions. 42 | 43 | **Finding and opening existing files:** 44 | Note that there are two file selection mechanisms. "File Explorer" and "System Exlorer". "File Explorer" is now inside the overflow menu. 45 | The "System Explorer" appears as a "+" icon on the action bar. __File Explorer__ will probably only work 46 | with files that Android considers "internal". This is due to changes in Android permissons starting with Android 4.4. 47 | However, the __File Explorer__ is probably more efficient than __System Explorer__, so if you can get it to work that might be the first choice. In future editions, 48 | the __File Explorer__ may be disabled for Android 5 and greater. 49 | 50 | A long press on the line item in the list view will bring up a dialog where you can change the display name (not the internal TiddlyWiki name), mark the file as browsable, mark the file as a clipboard, or mark the file to be removed (in which case, the file will be removed as soon as you confirm your choice). 51 | 52 | When you've made modifications to a TW file and need the page to refresh, you have two options. The first is that you can use the "exit" button to exit all of the way out of Quinoid. Then return to Quinoid and continue as before. This seems to work. 53 | 54 | The other is to import the small tiddler, "Refresh-for-Quinoid.json", which should be available along with the releases. After importing and refreshing (either before loading into Quinoid, or by backing out of Quinoid as outline above), you should be able to use the normal refresh/reload buttons made available by TW. 55 | 56 | ### Capturing/sharing text with Quinoid 57 | 58 | You can send (share) text with Quinoid, though I don't know how robust it is, so test carefully. 59 | 60 | In Quinoid, you can long-press on an item and select it to be a "clipboard" for Quinoid. If you do not make a selection, then Quinoid will use the first item in the list by default. 61 | 62 | In your 3rd-party application, select some text the usual way and then choose to "share". From the selection menu, pick Quinoid. The screen will go blank momentarily before returning to your app. Behind the scenes, it's activating Quinoid and saving your text. 63 | 64 | Back in Quinoid, navigate to your target page. After the page loads you should wait until it says "wiki saved." If you check under the "recent" tab, you'll see that there is one or more tiddlers with the name "Quinoid-Clip-". Currently (vsn 0.0.007) all the text that you shared will be in one tiddler. A new tiddler will not be created until you have visited the page once. 65 | 66 | Various notes and warnings: I don't know if it matters whether Quinoid is launched for this to work. Perhaps people can provide feedback. I've tested it with Quinoid always launched. I also don't know if it matters whether you are the dashboard page or on the browse pages, though it seems to work for me either way. Awaiting feedback. 67 | 68 | Note really well: The text that is captured is *not* transferred to the target TW file until you visit it. So if you use some other tool (e.g. web-browser) to visit the page, the new text will not be available. 69 | 70 | ### Using the File downloader and auto-loading of TW-files. 71 | 72 | Under the activity-bar 3-dot menu, you can select "Resources" which will allow you to download one or more pre-existing starter files. Click on "Resources" and then click on whatever items you are interested in. If you are connected to the internet the downloads should start immediately. The file(s) will be saved to a TwFiles sub-folder of your public documents folder. This subfolder may be on your physical internal drive or your external drive, depending on Android version and manufacturer. I targeted the external drive, but as far as I can tell, I (the developer) don't have full control. I'll be interested in hearing reports back. 73 | 74 | After making your selections, use the "back" button of your device to return to the main TW file browse list. The new files should appear within a few moments, if all goes well. 75 | 76 | Whatever location is chosen, any TW files (or html files) placed in the TwFiles sub-directory will be automatically added to Quinoid's file list upon start-up. 77 | 78 | ### Configuring Downloadable File List 79 | 80 | The first time Quinoid is loaded on your device, a new resource file (TwResources.json) will be created in the same directory mentioned above for downloading resource files. It contains a handful of existing sites (including possibly this help file). This file is in JSON format. If you are comfortable with JSON, you can edit the JSON file to create your own list of starter files. For instance, you might want to have an "empty" pre-release file, rather than an empty release file to work with. Or, if you are a gamer, you might want to download a new gaming TW for each game you want to track. Or you might provide your own curated list of files to friends and colleagues. 81 | 82 | Currently the TwResources file is very simple, containing only the four properties, "id", "title", "description", and "filestem": 83 | 84 | * "id" represents the URL to the file you wish to download. 85 | * "title" is the short title that will be displayed in the menu 86 | * "description" is a longer field for explaining the nature of the target HTML file. 87 | * "filestem" is the generic, simple file name for the file without the ".html" ending. It is used in naming the downloaded file, since the source URL will often not have a suitable, simple name. 88 | 89 | The main thing to keep in mind is that the file must follow the "real" rules of JSON. Certain characters may have to be escaped. In particular, when making file paths, each forward slash (/) must be prefixed by a backward slash, thusly: \/ . Breaking the JSON will probably mean that no menu will appear when you click on the "Resources" menu item. 90 | 91 | ### Notes about GDrive 92 | 93 | I don't use GDrive myself very much. I've only been testing it in emulators. The behavior in emulators may be different than the behavior on actual devices. Based on several observations, it appears possible that a TW file that has been accessed by Quinoid from GDrive may not send back a date stamp. Thus it appears a prudent course to follow after using GDrive in offline mode goes something like: 94 | 95 | 1. Exit out of Quinoid (via the exit option) to flush any current work. 96 | 2. Visit the GDrive app, and restore your file to "online" 97 | 3. Click on the "Reload/Recycle" button. GDrive will announce that it is uploading your file. 98 | 4. Switch your file to "offline" again if you continue to work in Quinoid. 99 | 100 | It also appears that you don't need to use your file in offline mode at all if you are continously connected to the internet. However, I haven't tested this and am not sure what happens when you "walk" out of internet range. In all cases, be careful. Don't commit any work that you really want to keep.# Quinoid 101 | 102 | -------------------------------------------------------------------------------- /Refresh-for-Quinoid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "created": "20190205225012670", 4 | "title": "Refresh-for-Quinoid", 5 | "type": "application/x-tiddler", 6 | "modified": "20190207002008555", 7 | "tags": "$:/tags/RawMarkup", 8 | "text": "" 9 | } 10 | ] -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 53 | 54 | 58 | 62 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/assets/TwResources.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id":"https:\/\/marxsal.github.io\/quinoid\/","title":"Help (Readme) for Quinoid","description":"The readme file for Quinoid contains some hints how to use Quinoid. It is not a TW file.","filestem":"readme"}, 3 | {"id":"https:\/\/tiddlywiki.com\/empty","title":"Empty (Current Release)","description":"An empty edition of the current release from the TiddlyWiki site.","filestem":"empty"}, 4 | {"id":"https:\/\/tid.li\/tw5\/tdn2019.html" ,"title":"TODO NOW","description":"Clean, neat task manager for TW","filestem":"todonow"}, 5 | {"id":"http:\/\/j.d.ml.tiddlyspot.com" ,"title":"Expense Tracker (JD, alpha)","description":"An expense tracker for TW (alpha - in development)","filestem":"expense"} ] 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/DisplayMessageActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.widget.TextView; 7 | 8 | public class DisplayMessageActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_display_message); 14 | Intent intent = getIntent() ; 15 | String message = intent.getStringExtra(TwActivity.EXTRA_MESSAGE); 16 | 17 | // Capture the textview and set the string as its text 18 | TextView textView = findViewById(R.id.textView); 19 | textView.setText(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.Manifest; 4 | import android.app.DownloadManager; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.pm.PackageManager; 10 | import android.graphics.Bitmap; 11 | import android.graphics.BitmapFactory; 12 | import android.net.Uri; 13 | import android.os.Environment; 14 | import android.support.annotation.NonNull; 15 | import android.support.annotation.Nullable; 16 | import android.support.v4.app.ActivityCompat; 17 | import android.support.v4.app.DialogFragment; 18 | import android.support.v4.content.ContextCompat; 19 | import android.support.v4.view.MenuCompat; 20 | import android.support.v7.app.AppCompatActivity; 21 | import android.os.Bundle; 22 | import android.util.Log; 23 | import android.view.Menu; 24 | import android.view.MenuItem; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.AdapterView; 28 | import android.widget.ArrayAdapter; 29 | import android.widget.CheckBox; 30 | //import android.widget.ListView; 31 | import android.widget.ImageView; 32 | import android.widget.ListView; 33 | import android.widget.TextView; 34 | 35 | import com.nononsenseapps.filepicker.FilePickerActivity; 36 | import com.nononsenseapps.filepicker.Utils; 37 | 38 | import java.io.File; 39 | import java.util.ArrayList; 40 | import java.util.HashMap; 41 | import java.util.List; 42 | import java.util.Map; 43 | 44 | public class TwActivity extends AppCompatActivity implements TwDialogFragment.TwDialogFragmentListener { 45 | public static final String LOG_TAG = "32XND-TwActivity"; 46 | public static final String DIALOG_TAG = "FirstDialogTag"; 47 | public static final String LAUNCH_PAGE = "PagerView Launch Page Position"; 48 | public static final String EXTRA_MESSAGE = "com.example.my32ndapplication.MESSAGE"; 49 | public static final String AUTHORITY = "com.example.my32ndapplication.provider"; // Needed by FileUtils2 and file provider 50 | public static final String RESOURCE_LIST_FILE = "TwResources.json"; 51 | 52 | 53 | public static boolean ReadWritePermissionGranted ; 54 | public static final long REFERENCE_UNAVAILABLE = -1 ; 55 | public static final String TW_SUBDIR = "TwFiles"; 56 | public static TwUtils sTwUtils ; 57 | public static final int REQUEST_WRITE_EXTERNAL_STORAGE = 42; 58 | final int REQUEST_FILE_OPEN = 2; 59 | final int NNF_FILEPICKER = 3; 60 | private long downloadReference ; 61 | 62 | private ArrayList mTwFiles; 63 | 64 | private ArrayList mTwResources ; // ONLY DURING TESTING. THIS SHOULD GO INTO THE ASSOCIATED DIALOG CLASS 65 | 66 | public static Map sTwDownloads = new HashMap(); 67 | @Override 68 | protected void onCreate(Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | 71 | // Code to shutdown app completely -- using "finish" inside of back-button code was unsuccessfull 72 | // ... or maybe it was fine. Hard to tell 73 | Intent intent = getIntent(); 74 | if (intent.hasExtra("exit")) finish(); 75 | 76 | sTwUtils = TwUtils.get(this); 77 | 78 | 79 | if (ContextCompat.checkSelfPermission(this, 80 | Manifest.permission.WRITE_EXTERNAL_STORAGE) 81 | != PackageManager.PERMISSION_GRANTED) { 82 | Log.d(LOG_TAG, "It thinks it needs write permission. I'll try asking."); 83 | ActivityCompat.requestPermissions((TwActivity) this, 84 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 85 | TwActivity.REQUEST_WRITE_EXTERNAL_STORAGE); 86 | } else { 87 | ReadWritePermissionGranted = true ; 88 | } 89 | 90 | 91 | 92 | sTwUtils.copySpecificAssets(); 93 | 94 | //String resourceFile = (new File(sTwUtils.getTWDocumentPath(TW_SUBDIR),RESOURCE_LIST_FILE)).toString() ; 95 | // Log.d(LOG_TAG, "I see resource file name: " + resourceFile); 96 | // 97 | // mTwResources = TwResource.loadTwResourceFromJSON(resourceFile); 98 | // 99 | // if(mTwResources == null || mTwResources.isEmpty()) { 100 | // 101 | // Log.d(LOG_TAG, "Resources empty!"); 102 | // } 103 | // Log.d(LOG_TAG, "Resources retrieved, maybe"); 104 | // TwResource tempResource = mTwResources.get(0); 105 | // if(tempResource != null) 106 | // Log.d(LOG_TAG, "Resource title is: " + tempResource.getTitle()); 107 | 108 | 109 | 110 | mTwFiles = TwManager.get(this).getTwFiles(); 111 | 112 | // Get action and MIME type 113 | String intentAction = intent.getAction(); 114 | String intentType = intent.getType(); 115 | // Uri intentData = intent.getData() ; 116 | if (Intent.ACTION_SEND.equals(intentAction) && intentType != null) { 117 | 118 | if (intent.getType() != null && intent.getType().equals("text/plain")) { 119 | 120 | Log.d(LOG_TAG, "I see incoming intent !!"); 121 | 122 | int position = TwManager.get(this).getClipboardIndex() ; 123 | TwFile twFile = mTwFiles.get(position); 124 | 125 | String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); 126 | String message = twFile.getMessage() + "\n" + sharedText; 127 | 128 | Log.d(LOG_TAG, "Setting up return message: " + message); 129 | twFile.setMessage(message); 130 | 131 | // TODO: TW-SHARE After stashing shared value, SAVE the state of all tw files 132 | TwManager.get(this).saveTwFilesToJSON() ; 133 | finish(); 134 | } 135 | } 136 | 137 | setContentView(R.layout.activity_main); 138 | 139 | registerReceiver(broadcastReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 140 | 141 | //TwManager.get(this).loadTwFilesFromPreferences(); 142 | 143 | 144 | 145 | 146 | 147 | ListView listView = findViewById(R.id.listview); 148 | // ArrayAdapter adapter = 149 | // new ArrayAdapter(this, android.R.layout.simple_list_item_1, mTwFiles); 150 | TwFileAdapter adapter = 151 | new TwFileAdapter(mTwFiles); 152 | 153 | listView.setAdapter(adapter); 154 | 155 | // Create a message handling object as an anonymous class. 156 | AdapterView.OnItemClickListener mMessageClickedHandler = new AdapterView.OnItemClickListener() { 157 | public void onItemClick(AdapterView parent, View v, int position, long id) { 158 | 159 | if (TwManager.get(TwActivity.this).getBrowsableFiles().size() < 1) { 160 | sTwUtils.makeToast("There are no files marked for browsing."); 161 | return; 162 | } 163 | 164 | if (TwManager.get(TwActivity.this).anyFileBased()) { 165 | if (ContextCompat.checkSelfPermission(TwActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) 166 | != PackageManager.PERMISSION_GRANTED) { 167 | Log.d(LOG_TAG, "onItemClickListener thinks there are file-type items and NO PERMISSION GRANTED."); 168 | } 169 | } 170 | Log.d(LOG_TAG, "onItemClickListener - about to request permissions."); 171 | 172 | TwFile twFile = mTwFiles.get(0); 173 | Intent intent = new Intent(TwActivity.this, TwPagerActivity.class); 174 | //intent.putExtra(TwFragment.TW_FILE_NAME, twFile.getTitle()); 175 | intent.putExtra(LAUNCH_PAGE, position); 176 | startActivity(intent); 177 | // Do something in response to the click 178 | 179 | } 180 | }; 181 | listView.setOnItemClickListener(mMessageClickedHandler); 182 | 183 | //DONE: 00 Make dialog menu on long press 184 | // Handle long presses 185 | AdapterView.OnItemLongClickListener mItemLongClickListener = new AdapterView.OnItemLongClickListener() { 186 | @Override 187 | public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { 188 | 189 | // TwManager.get(TwActivity.this).deleteTwFile(i); 190 | 191 | TwDialogFragment dialogFragment = TwDialogFragment.newInstance(i); 192 | dialogFragment.show(getSupportFragmentManager(), DIALOG_TAG); 193 | ListView listView = findViewById(R.id.listview); 194 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 195 | return true; 196 | } 197 | }; 198 | listView.setOnItemLongClickListener(mItemLongClickListener); 199 | 200 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 201 | REQUEST_WRITE_EXTERNAL_STORAGE); 202 | 203 | } 204 | 205 | @Override 206 | public boolean onCreateOptionsMenu(Menu menu) { 207 | getMenuInflater().inflate(R.menu.menu_main, menu); 208 | MenuCompat.setGroupDividerEnabled(menu,true); 209 | return super.onCreateOptionsMenu(menu); 210 | } 211 | 212 | @Override 213 | public boolean onOptionsItemSelected(MenuItem item) { 214 | switch (item.getItemId()) { 215 | case R.id.launch_sys_explorer: 216 | launchSystemExplorer(); 217 | return true; 218 | case R.id.launch_local_explorer : 219 | launchLocalExplorer(); 220 | return true ; 221 | case R.id.download_empty : 222 | 223 | //downloadTW(); 224 | Log.d(LOG_TAG, "onOptions... download empty"); 225 | if (!TwUtils.isConnected(this)) { 226 | sTwUtils.makeToast("No internet connected."); 227 | return true; 228 | } 229 | 230 | Intent intentResource = new Intent(this, TwResourceActivity.class); 231 | //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 232 | //intent.putExtra("exit", true); 233 | startActivity(intentResource); 234 | 235 | // String twFilePath = sTwUtils.getTWInternalStoragePathname("internals") ; 236 | // if ( twFilePath == null ) { 237 | // sTwUtils.makeToast("Not able to obtain storage."); 238 | // return true; 239 | // } 240 | 241 | // String fileName = sTwUtils.makeRandomizedFileName("empty", ".html"); 242 | // File dirPath = sTwUtils.getTWDocumentPath(TW_SUBDIR); 243 | // downloadReference = DownloadTw("https://tiddlywiki.com/empty", dirPath, fileName ,"Basic - empty TW") ; 244 | 245 | return true ; 246 | 247 | case R.id.menu_exit : 248 | 249 | Intent intent = new Intent(this, TwActivity.class); 250 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 251 | intent.putExtra("exit", true); 252 | startActivity(intent); 253 | 254 | // DONE: Menu Exit option (TW-DOWNLOADS) 255 | 256 | default: 257 | 258 | return super.onOptionsItemSelected(item); 259 | } 260 | } 261 | 262 | 263 | 264 | 265 | // DONE: Move code from onBackPressed to exit option (TW-DOWNLOADS #CURRENT) 266 | // @Override 267 | // public void onBackPressed() { 268 | // Intent intent = new Intent(this, TwActivity.class); 269 | // intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 270 | // intent.putExtra("exit", true); 271 | // startActivity(intent); 272 | // //super.onBackPressed(); 273 | // } 274 | 275 | @Override 276 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 277 | Log.d(LOG_TAG, "onRequestPermissionResult -- receiving something"); 278 | switch (requestCode) { 279 | case REQUEST_WRITE_EXTERNAL_STORAGE: { 280 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 281 | Log.d(LOG_TAG, "onRequestPermissionResult says permission granted."); 282 | ReadWritePermissionGranted = true ; 283 | TwManager.get(this).addFromDocumentDir(this); 284 | } else { 285 | Log.d(LOG_TAG, "onRequestPermissionResult says permission NOT granted."); 286 | } 287 | } 288 | } 289 | } 290 | 291 | 292 | @Override 293 | protected void onStart() { 294 | super.onStart(); 295 | } 296 | 297 | public void launchLocalExplorer() { 298 | //DONE: Change method name 299 | // TODO: Need to update NNF library to filter out non-HTML files 300 | Intent i = new Intent(this, FilePickerActivity.class); 301 | // This works if you defined the intent filter 302 | // Intent i = new Intent(Intent.ACTION_GET_CONTENT); 303 | 304 | // Set these depending on your use case. These are the defaults. 305 | i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, true); 306 | i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false); 307 | i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE); 308 | 309 | // Configure initial directory by specifying a String. 310 | // You could specify a String like "/storage/emulated/0/", but that can 311 | // dangerous. Always use Android's API calls to get paths to the SD-card or 312 | // internal memory. 313 | i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath()); 314 | 315 | startActivityForResult(i, NNF_FILEPICKER); 316 | 317 | } 318 | 319 | public void launchLocalExplorer(View view) { 320 | launchLocalExplorer(); 321 | } 322 | 323 | @Override 324 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 325 | super.onActivityResult(requestCode, resultCode, data); 326 | // This code may be obsolete if the file-picker approach works. 327 | // But may be good backup as various file-pickers come and go 328 | if (requestCode == REQUEST_FILE_OPEN && resultCode == RESULT_OK) { 329 | 330 | Uri uriFile = data.getData(); 331 | 332 | // Indicate we really want to keep these documents 333 | final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 334 | getContentResolver().takePersistableUriPermission(uriFile, takeFlags); 335 | 336 | //TwManager.get(this).addTwFile(new TwFile(uriFile.getLastPathSegment())); 337 | TwManager.get(this).addTwFile(new TwFile(this, uriFile.toString())); 338 | 339 | // Launch something 2019-01-10 We will need this code in the 340 | // listview listener, maybe 341 | ListView listView = findViewById(R.id.listview); 342 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 343 | 344 | } 345 | 346 | if (requestCode == NNF_FILEPICKER && resultCode == RESULT_OK) { 347 | // Use the provided utility method to parse the result 348 | List uriFiles = Utils.getSelectedFilesFromResult(data); 349 | for (Uri uriFile : uriFiles) { 350 | //File file = Utils.getFileForUri(uriFile); 351 | String path = uriFile.getPath(); 352 | int rootpos = path.indexOf("/root/"); 353 | if (rootpos != -1) { 354 | path = path.substring(6); 355 | } 356 | //path = "file:///" + path ; 357 | TwManager.get(this).addTwFile(new TwFile(this, path)); 358 | // Launch something 2019-01-10 We will need this code in the 359 | // listview listener, maybe 360 | ListView listView = findViewById(R.id.listview); 361 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 362 | } 363 | } 364 | 365 | 366 | } 367 | 368 | 369 | 370 | @Override 371 | protected void onPause() { 372 | super.onPause(); 373 | Log.d(LOG_TAG, "TA:onPause "); 374 | //TwManager.get(this).saveTwFilesToPreferences(); 375 | TwManager.get(this).saveTwFilesToJSON(); 376 | } 377 | 378 | @Override 379 | protected void onDestroy() { 380 | super.onDestroy(); 381 | unregisterReceiver(broadcastReceiver); 382 | Log.d(LOG_TAG, "onDestroy"); 383 | } 384 | 385 | @Override 386 | protected void onResume() { 387 | super.onResume(); 388 | TwManager.get(this).saveTwFilesToJSON(); 389 | Log.d(LOG_TAG, "onResume - saving to JSON complete."); 390 | ListView listView = findViewById(R.id.listview); 391 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 392 | } 393 | 394 | public String getPublicAlbumStorageDir(String albumName) { 395 | // Get the directory for the user's public pictures directory. 396 | File file = new File(Environment.getExternalStoragePublicDirectory( 397 | Environment.DIRECTORY_DOCUMENTS), albumName); 398 | if (!file.mkdirs()) { 399 | Log.d(LOG_TAG, "Directory not created"); 400 | } 401 | 402 | return file.getPath(); 403 | } 404 | 405 | 406 | public void launchSystemExplorer() { //DONE: Change name 407 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 408 | //intent.setType("file/*"); 409 | intent.setType("text/*"); 410 | //intent.setType("text/html,text/htm,text.txt,html/tw"); 411 | //intent.setType("*/*"); 412 | // String[] mimetypes = {"text/html", "text/htm", "html/tw"}; 413 | // intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); 414 | 415 | intent.addCategory(Intent.CATEGORY_OPENABLE); 416 | // Only the system receives the ACTION_OPEN_DOCUMENT, so no need to test. 417 | startActivityForResult(intent, REQUEST_FILE_OPEN); 418 | //launchLocalExplorer(); 419 | 420 | /* TwFile twFile = mTwFiles.get(0) ; 421 | Intent intent = new Intent(TwActivity.this, TwPagerActivity.class) ; 422 | intent.putExtra(TwFragment.TW_FILE_NAME, twFile.getTitle()); 423 | startActivity(intent);*/ 424 | } 425 | 426 | // This version needed for easy button mapping with layout resource 427 | public void launchSystemExplorer(View view) { //DONE: Change name 428 | 429 | launchSystemExplorer(); 430 | } 431 | 432 | @Override 433 | public void onDialogNegativeClick(DialogFragment dialog) { 434 | // Nothing really to do, yet 435 | } 436 | 437 | @Override 438 | public void onDialogPositiveClick(DialogFragment dialog) { 439 | Log.d(LOG_TAG, "TA-onDialogPositiveClick: I see display title"); 440 | notifyListAdapter(); 441 | } 442 | 443 | public void notifyListAdapter() { 444 | ListView listView = findViewById(R.id.listview); 445 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 446 | } 447 | 448 | 449 | 450 | private class TwFileAdapter extends ArrayAdapter { 451 | 452 | public TwFileAdapter(ArrayList twFiles) { 453 | super(TwActivity.this, 0, twFiles); 454 | } 455 | 456 | @Override 457 | public View getView(int position, View convertView, ViewGroup parent) { 458 | if (convertView == null) { 459 | convertView = getLayoutInflater().inflate(R.layout.list_item, null); 460 | } 461 | TwFile twFile = mTwFiles.get(position); 462 | TextView titleView = (TextView) convertView.findViewById(R.id.itemtitle); 463 | titleView.setText(twFile.toString()); 464 | CheckBox checkBoxBrowse = (CheckBox) convertView.findViewById(R.id.checkboxBrowse); 465 | checkBoxBrowse.setChecked(twFile.isBrowsable()); 466 | 467 | CheckBox checkBoxClipboard = (CheckBox) convertView.findViewById(R.id.checkboxClip); 468 | checkBoxClipboard.setChecked(twFile.isClipboard()); 469 | 470 | if (twFile.getIconPath() != null && !twFile.getIconPath().isEmpty()) { 471 | ImageView imageView = (ImageView) convertView.findViewById(R.id.iconView); 472 | Bitmap bitmap = BitmapFactory.decodeFile(twFile.getIconPath()); 473 | imageView.setImageBitmap(bitmap); 474 | } 475 | return convertView; 476 | } 477 | } 478 | 479 | 480 | BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 481 | 482 | public void onReceive(Context ctxt, Intent intent) { 483 | 484 | 485 | long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); 486 | 487 | 488 | Log.d(LOG_TAG, "Broadcast received:" + referenceId); 489 | 490 | TwFile twFile = sTwDownloads.get(new Long(referenceId)); 491 | if(twFile != null ) { 492 | Log.d(LOG_TAG, "broadcastReceiver - Adding twfile to list"); 493 | TwManager.get(TwActivity.this).addTwFile(twFile); 494 | TwManager.get(TwActivity.this).saveTwFilesToJSON() ; 495 | ListView listView = findViewById(R.id.listview); 496 | ((ArrayAdapter) listView.getAdapter()).notifyDataSetChanged(); 497 | sTwDownloads.remove(new Long(referenceId)) ; 498 | 499 | } 500 | //list.remove(referenceId); 501 | 502 | 503 | // if (list.isEmpty()) 504 | // { 505 | // 506 | // } 507 | 508 | } 509 | }; 510 | 511 | } 512 | 513 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.v4.app.DialogFragment; 11 | import android.util.Log; 12 | import android.view.View; 13 | import android.widget.CheckBox; 14 | import android.widget.EditText; 15 | import android.widget.TextView; 16 | 17 | public class TwDialogFragment extends DialogFragment { 18 | static final String EXTRA_POSITION = "twFileIndex"; 19 | static final String LOG_TAG = "32XND - TwDialogFragment"; 20 | 21 | TwFile mTwFile; 22 | EditText mEditDisplayTitle ; 23 | CheckBox mCheckBoxDialogBrowse; 24 | CheckBox mCheckBoxDialogClipboard; 25 | CheckBox mCheckBoxDialogDelete; 26 | 27 | TwDialogFragmentListener mListener ; 28 | 29 | @NonNull 30 | @Override 31 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 32 | int twFileIndex = (int) getArguments().getInt(EXTRA_POSITION); 33 | mTwFile = TwManager.get(getActivity()).getTwFile(twFileIndex); 34 | String display = mTwFile.getDisplayName(); 35 | boolean browseAble = mTwFile.isBrowsable(); 36 | boolean clipboard = mTwFile.isClipboard(); 37 | 38 | View v = getActivity().getLayoutInflater().inflate(R.layout.tw_fragment_dialog, null); 39 | 40 | mEditDisplayTitle = v.findViewById(R.id.editDescription); 41 | mEditDisplayTitle.setText(display,TextView.BufferType.EDITABLE); 42 | 43 | mCheckBoxDialogBrowse = v.findViewById(R.id.checkBoxDialogBrowse); 44 | mCheckBoxDialogBrowse.setChecked(browseAble); 45 | 46 | mCheckBoxDialogClipboard = v.findViewById(R.id.checkBoxDialogClip); 47 | mCheckBoxDialogClipboard.setChecked(clipboard); 48 | 49 | mCheckBoxDialogDelete = v.findViewById(R.id.checkBoxDialogDelete); 50 | 51 | return new AlertDialog.Builder(getActivity()) 52 | .setView(v) 53 | .setPositiveButton(R.string.dialog_confirm_button, new DialogInterface.OnClickListener() { 54 | @Override 55 | public void onClick(DialogInterface dialogInterface, int i) { 56 | String display = mEditDisplayTitle.getText().toString(); 57 | Log.d(LOG_TAG, "TDF...onClick: I see display title: " + display); 58 | if (display != null ) { 59 | mTwFile.setDisplayName(display); 60 | Log.d(LOG_TAG, "TDF...onClick: I just set display title: " + display); 61 | 62 | } 63 | if(mCheckBoxDialogClipboard.isChecked()) { 64 | TwManager.get(getActivity()).setAsClipboard(mTwFile); 65 | Log.d(LOG_TAG, "I'm trying to mark clipboard"); 66 | } 67 | else 68 | mTwFile.setIsClipboard(false); 69 | 70 | mTwFile.setIsBrowsable(mCheckBoxDialogBrowse.isChecked()); 71 | if(mCheckBoxDialogDelete.isChecked()) TwManager.get(getActivity()).deleteTwFile(mTwFile); 72 | mListener.onDialogPositiveClick(TwDialogFragment.this); 73 | } 74 | } 75 | ) 76 | 77 | .setNegativeButton(R.string.dialog_cancel_button, null) 78 | .create(); 79 | } 80 | 81 | public static TwDialogFragment newInstance(int twFileIndex) { 82 | Bundle args = new Bundle(); 83 | args.putInt(EXTRA_POSITION,twFileIndex); 84 | TwDialogFragment fragment = new TwDialogFragment() ; 85 | fragment.setArguments(args); 86 | return fragment ; 87 | } 88 | 89 | public interface TwDialogFragmentListener { 90 | public void onDialogPositiveClick(DialogFragment dialog) ; 91 | public void onDialogNegativeClick(DialogFragment dialog) ; 92 | } 93 | 94 | @Override 95 | public void onAttach(Context context) { 96 | super.onAttach(context); 97 | try { 98 | mListener = (TwDialogFragmentListener) context; 99 | } catch (ClassCastException e) { 100 | throw new ClassCastException(getActivity().toString() + " must implement TwDialogFragmentListener"); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwFile.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.net.Uri; 7 | import android.util.Base64; 8 | import android.util.Log; 9 | 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.io.OutputStreamWriter; 19 | import java.io.Writer; 20 | import java.util.UUID; 21 | 22 | public class TwFile { 23 | public static final String LOG_TAG = "32XND-TwFile"; 24 | public static final String DEFAULT_TITLE = "(Title unknown)"; 25 | private static final String JSON_ID = "id"; 26 | private static final String JSON_TITLE = "title"; 27 | private static final String JSON_IS_CONTENT = "iscontent"; 28 | private static final String JSON_IS_BROWSABLE = "isbrowsable"; 29 | private static final String JSON_DISPLAY_TITLE = "displaytitle"; 30 | private static final String JSON_ICON_PATH = "iconpath"; 31 | private static final String JSON_IS_CLIPBOARD = "isclipboard"; 32 | private static final String JSON_MESSAGE = "message"; 33 | 34 | //DONE - SAVE JSON for CLIPBOARD 35 | //DONE - Make methods in TW Manager to select and set just one TW as clipboard 36 | 37 | private String mTitle = DEFAULT_TITLE; 38 | private String mId; 39 | private String mDisplay ="" ; // User-provided name to display in list 40 | private boolean mIsContentType; // private Context context; 41 | private boolean mIsBrowsable ; // Will we be loading a page for this item? 42 | private String mIconPath = "" ; 43 | private String mMessage = "" ; // Message/clip to be added to file once opened 44 | private boolean mIsClipboard ; // Indicates this TW is to be target of clipboard. TW Manager should make sure only one is chosen. 45 | 46 | public String getMessage() { 47 | return mMessage; 48 | } 49 | 50 | public void setMessage(String mMessage) { 51 | this.mMessage = mMessage; 52 | } 53 | 54 | // This is the path to the physical file that we are using in WebView 55 | // but without the file:/// part because we need that to create streams 56 | // for saving. WebView requires a path with "file:///" in it. 57 | private String unschemedFilePath; // Set when loading 58 | 59 | public String getDisplayName() { 60 | return mDisplay; 61 | } 62 | 63 | public void setDisplayName(String mDisplay) { 64 | this.mDisplay = mDisplay; 65 | } 66 | 67 | public boolean isBrowsable() { 68 | return mIsBrowsable; 69 | } 70 | 71 | public void setIsBrowsable(boolean mIsBrowsable) { 72 | this.mIsBrowsable = mIsBrowsable; 73 | } 74 | 75 | public boolean isIsContentType() { 76 | return mIsContentType; 77 | } 78 | 79 | 80 | public boolean isClipboard() { 81 | return mIsClipboard; 82 | } 83 | 84 | public void setIsClipboard(boolean mIsClipboard) { 85 | this.mIsClipboard = mIsClipboard; 86 | } 87 | 88 | // Constructor, I hope. Used when resource/filepath is first selected 89 | // from pick list. 90 | TwFile(Context context , String resourceString) { 91 | 92 | // *********************************************** 93 | // Start Beta. These are for development only 94 | File tempFile = context.getFilesDir(); 95 | // File tempFile = context.getDir("provided", Context.MODE_PRIVATE).getAbsoluteFile() ; 96 | StringBuilder sb = new StringBuilder(); 97 | int cnt = 0 ; 98 | for(File reallyTempFile : tempFile.listFiles() ) { 99 | if(reallyTempFile.isFile()) sb.append("F "+reallyTempFile.getName()+"\n") ; 100 | if(reallyTempFile.isDirectory()) sb.append("D "+reallyTempFile.getName()+"\n") ; 101 | cnt++; 102 | } 103 | //Log.d(LOG_TAG,sb.toString() + "There were " + Integer.toString(cnt) + " files in temp directory.") ; 104 | 105 | // End Beta 106 | // *********************************************** 107 | 108 | //Log.d(LOG_TAG, "Constructor: Seeing resource string: " + resourceString); 109 | setId(resourceString); 110 | //setContext(context); 111 | mIsContentType = false ; 112 | setTitle(DEFAULT_TITLE); 113 | setIsBrowsable(true); 114 | 115 | if(mId.startsWith("file")) { 116 | // setUnschemedFilePath will strip off "file" 117 | setUnschemedFilePath(mId); 118 | return; 119 | } 120 | if(mId.startsWith("content")) { 121 | //Log.d(LOG_TAG, "About to call loadFilePath"); 122 | mIsContentType = true ; 123 | loadFilePath(context) ; 124 | return ; 125 | } 126 | 127 | //Log.d(LOG_TAG, "I dont think content starts with 'content', so I'll pretend it's a regular file."); 128 | setUnschemedFilePath(mId); 129 | } 130 | 131 | // Constructor #2 132 | public TwFile(Context c, JSONObject json) throws JSONException { 133 | mId = json.getString(JSON_ID); 134 | mTitle = json.getString(JSON_TITLE); 135 | mIsContentType = json.getBoolean(JSON_IS_CONTENT); 136 | mDisplay = json.getString(JSON_DISPLAY_TITLE); 137 | mIsBrowsable = json.getBoolean(JSON_IS_BROWSABLE); 138 | mIconPath = json.getString(JSON_ICON_PATH); 139 | mIsClipboard = json.getBoolean(JSON_IS_CLIPBOARD); 140 | mMessage = json.getString(JSON_MESSAGE) ; 141 | loadFilePath(c); 142 | if(!mId.startsWith("content")) setUnschemedFilePath(mId); 143 | } 144 | 145 | public void loadFilePath(Context c) { 146 | String newFilePath = "IF YOU CAN READ THIS SOMETHING WENT WRONG."; 147 | // We only need to sources that are content types. At least so far 148 | if(!mIsContentType) {return; } 149 | 150 | try { 151 | //Log.d(LOG_TAG, "Trying to create file name."); 152 | newFilePath = File.createTempFile("TIDDLYWIKISTUB", ".html", 153 | c.getDir("provided", Context.MODE_PRIVATE)).getAbsolutePath() ; 154 | //Log.d(LOG_TAG, "Created file name: " + newFilePath); 155 | int cnt = 0 ; // For DEBUG 156 | setUnschemedFilePath(newFilePath); 157 | Uri uriFile = Uri.parse(mId); 158 | InputStream inputStream = c.getContentResolver().openInputStream(uriFile); 159 | OutputStream outputStream = new FileOutputStream(newFilePath); 160 | 161 | byte[] buf = new byte[1024]; 162 | int len; 163 | while ((len = inputStream.read(buf)) > 0) { 164 | cnt++ ; 165 | outputStream.write(buf, 0, len); 166 | } 167 | 168 | outputStream.flush(); 169 | outputStream.close(); 170 | inputStream.close(); 171 | //Log.d(LOG_TAG, "I counted " + cnt + "k bytes"); 172 | } catch (IOException e) { 173 | Log.d(LOG_TAG, "IOException: " + e.getMessage()); 174 | e.printStackTrace(); 175 | 176 | } catch (Exception e) { 177 | Log.e(LOG_TAG, "Something not an IOE happened: " + e.getMessage()); 178 | } 179 | //Log.d(LOG_TAG, "I think I've successfully initialzed working file " + getUnschemedFilePath()); 180 | 181 | } 182 | 183 | public void saveFile(String text,Context c) { 184 | 185 | try { 186 | //Log.d(LOG_TAG, "Attempting to stream to: " + getUnschemedFilePath()); 187 | FileOutputStream outputStream = new FileOutputStream(getUnschemedFilePath()); 188 | outputStream.write(text.getBytes()); 189 | outputStream.close(); 190 | } catch (Exception e) { 191 | //Log.d(LOG_TAG, "IOE?: See stacktrace." + e.getMessage()); 192 | e.printStackTrace(); 193 | } 194 | 195 | if (mIsContentType) { 196 | try { 197 | OutputStream outputStream = c.getContentResolver().openOutputStream(Uri.parse(mId)); 198 | //OutputStream = new FileOutputStream(newFilePath); 199 | Writer writer = new OutputStreamWriter(outputStream, "UTF-8"); 200 | writer.write(text); 201 | writer.close(); 202 | } catch (IOException e) { 203 | Log.d(LOG_TAG, "saveFile ERR trying to write to original system file."); 204 | } 205 | } 206 | } 207 | 208 | public void setUnschemedFilePath(String filePath) { 209 | Log.d(LOG_TAG, "719X Before setting file path: " + filePath); 210 | filePath = filePath.replaceFirst("^file:/+","/") ; 211 | Log.d(LOG_TAG, "719X After setting file path: " + filePath); 212 | this.unschemedFilePath = filePath; 213 | } 214 | 215 | public String getUnschemedFilePath() { 216 | // Maybe this is same as absolute path. It's the path to the WORKING file which may be a temp file. 217 | return this.unschemedFilePath; 218 | } 219 | 220 | public String getTitle() { 221 | return mTitle; 222 | } 223 | public void setTitle(String pTitle) { 224 | // if(mTitle == null || mTitle.equals(DEFAULT_TITLE)) || mTitle.equals(pTitle){ 225 | //Log.d(LOG_TAG, "Setting mTitle to: " + pTitle); 226 | this.mTitle = pTitle; 227 | // } 228 | } 229 | 230 | // public Context getContext() { 231 | // return context; 232 | // } 233 | // 234 | // public void setContext(Context context) { 235 | // this.context = context; 236 | // } 237 | 238 | public String getId() { 239 | return mId; 240 | } 241 | 242 | public void setId(String id) { 243 | //Log.d(LOG_TAG, "Setting mId to: " + id); 244 | this.mId = id; 245 | } 246 | 247 | 248 | public String getIconPath() { 249 | return mIconPath; 250 | } 251 | 252 | public void makeIconAndPath(Context c, String bitmap64String) { 253 | Log.d(LOG_TAG, "saveIconPath " ); 254 | // I use 'save' because it's doing more than just setting in some cases. 255 | if( bitmap64String != null) { 256 | String iconPath = getIconPath() ; 257 | if (iconPath == null || iconPath.isEmpty()) iconPath = 258 | c.getDir("icons",c.MODE_PRIVATE).getAbsolutePath() + File.separator + "ico" + UUID.randomUUID().toString() + ".png"; 259 | File iconFile = new File(iconPath); 260 | //Log.d(LOG_TAG, "I am using icon name (abs): " + iconFile.getAbsolutePath()); 261 | 262 | byte[] decoded = Base64.decode(bitmap64String, Base64.DEFAULT); 263 | //Log.d(LOG_TAG, "After decode: " + decoded.toString()); 264 | //Log.d(LOG_TAG, "Decode length was: " + decoded.length); 265 | Bitmap bm = BitmapFactory.decodeByteArray(decoded, 0, decoded.length); 266 | try { 267 | //Log.d(LOG_TAG, "I am using icon name (can): " + iconFile.getCanonicalPath()); 268 | FileOutputStream fOS = new FileOutputStream(iconPath); 269 | bm.compress(Bitmap.CompressFormat.PNG, 100, fOS); 270 | fOS.close(); 271 | mIconPath = iconPath ; 272 | } catch (Exception e) { 273 | //Log.d(LOG_TAG, "Writing to icon got exception: " + e.getMessage()); 274 | e.printStackTrace(); //DEBUG 275 | } 276 | 277 | //bm.compress(Bitmap.CompressFormat.PNG,) 278 | } 279 | } 280 | 281 | 282 | @Override 283 | public String toString() { 284 | Log.d(LOG_TAG, "toString: I think display is this length: " + getDisplayName().length()); 285 | if (getDisplayName() != null && getDisplayName().length() > 1 ) return getDisplayName() ; 286 | if( getTitle() != null && !getTitle().startsWith(DEFAULT_TITLE)) return getTitle() ; 287 | return DEFAULT_TITLE + ": " + 288 | mId.substring(0,14) + "..." + 289 | mId.substring(mId.length()-14) ; 290 | 291 | } 292 | 293 | public JSONObject toJSON() throws JSONException { 294 | JSONObject json = new JSONObject(); 295 | json.put(JSON_ID, mId.toString()); 296 | json.put(JSON_TITLE, mTitle.toString()); 297 | json.put(JSON_IS_CONTENT, mIsContentType); 298 | json.put(JSON_DISPLAY_TITLE, mDisplay.toString()); 299 | json.put(JSON_IS_BROWSABLE, mIsBrowsable); 300 | json.put(JSON_ICON_PATH, mIconPath.toString()); 301 | json.put(JSON_IS_CLIPBOARD, mIsClipboard); 302 | json.put(JSON_MESSAGE, mMessage); 303 | 304 | return json; 305 | 306 | 307 | } 308 | 309 | 310 | } 311 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.annotation.TargetApi; 6 | import android.app.Activity; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.net.Uri; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.os.Debug; 13 | import android.os.Environment; 14 | import android.provider.MediaStore; 15 | import android.support.annotation.NonNull; 16 | import android.support.annotation.Nullable; 17 | import android.support.v4.app.ActivityCompat; 18 | import android.support.v4.app.Fragment; 19 | import android.support.v4.content.ContextCompat; 20 | import android.util.Base64; 21 | import android.util.JsonReader; 22 | import android.util.JsonToken; 23 | import android.util.Log; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.webkit.GeolocationPermissions; 28 | import android.webkit.ValueCallback; 29 | import android.webkit.WebChromeClient; 30 | import android.webkit.WebResourceRequest; 31 | import android.webkit.WebResourceResponse; 32 | import android.webkit.WebSettings; 33 | import android.webkit.WebView; 34 | import android.webkit.WebViewClient; 35 | 36 | import org.json.JSONObject; 37 | 38 | import java.io.BufferedReader; 39 | import java.io.File; 40 | import java.io.FileOutputStream; 41 | import java.io.IOException; 42 | import java.io.InputStream; 43 | import java.io.InputStreamReader; 44 | import java.io.Reader; 45 | import java.io.StringReader; 46 | import java.text.SimpleDateFormat; 47 | import java.util.Date; 48 | import java.util.Locale; 49 | 50 | import static android.app.Activity.RESULT_OK; 51 | 52 | 53 | public class TwFragment extends Fragment { 54 | public static final String LOG_TAG = "32XND-TwFragment"; 55 | public static String TW_FILE_NAME = "com.markqz.tw-fragment-file-name"; 56 | 57 | private String mCM; 58 | private ValueCallback mUM; 59 | private ValueCallback mUMA; 60 | private final static int FCR=5; 61 | //select whether you want to upload multiple files (set 'true' for yes) 62 | private boolean multiple_files = false; 63 | 64 | public String tw_file_name; 65 | WebView webView; 66 | TwFile twFile ; 67 | String javascriptEvalResult = ""; // A kluge to get around limits of inner classes 68 | 69 | 70 | public static TwFragment newInstance(String sFilename) { 71 | TwFragment fragment = new TwFragment(); 72 | Bundle bundle = new Bundle(); 73 | bundle.putString(TW_FILE_NAME,sFilename); 74 | fragment.setArguments(bundle); 75 | return fragment; 76 | } 77 | 78 | @Override 79 | public void onActivityResult(int requestCode, int resultCode, Intent intent){ 80 | super.onActivityResult(requestCode, resultCode, intent); 81 | if(Build.VERSION.SDK_INT >= 21){ 82 | Uri[] results = null; 83 | //checking if response is positive 84 | if(resultCode== RESULT_OK){ 85 | Log.d(LOG_TAG, "onAR - I think result OK"); 86 | if(requestCode == FCR){ 87 | if(null == mUMA){ 88 | return; 89 | } 90 | if(intent == null || intent.getData() == null){ 91 | if(mCM != null){ 92 | results = new Uri[]{Uri.parse(mCM)}; 93 | } 94 | }else{ 95 | String dataString = intent.getDataString(); 96 | if(dataString != null){ 97 | results = new Uri[]{Uri.parse(dataString)}; 98 | } else { 99 | if(multiple_files) { 100 | if (intent.getClipData() != null) { 101 | final int numSelectedFiles = intent.getClipData().getItemCount(); 102 | results = new Uri[numSelectedFiles]; 103 | for (int i = 0; i < numSelectedFiles; i++) { 104 | results[i] = intent.getClipData().getItemAt(i).getUri(); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | mUMA.onReceiveValue(results); 113 | mUMA = null; 114 | }else{ 115 | if(requestCode == FCR){ 116 | if(null == mUM) return; 117 | Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); 118 | mUM.onReceiveValue(result); 119 | mUM = null; 120 | } 121 | } 122 | } 123 | 124 | 125 | 126 | 127 | @SuppressLint("SetJavaScriptEnabled") 128 | @Nullable 129 | @Override 130 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 131 | @Nullable Bundle savedInstanceState) { 132 | tw_file_name = getArguments().getString(TW_FILE_NAME); 133 | View v = inflater.inflate(R.layout.tw_fragment, container, false); 134 | 135 | twFile = TwManager.get(getActivity()).getTwFile(tw_file_name) ; 136 | Log.d(LOG_TAG, "I am using id tw_file_name: " + tw_file_name + " and have found file " 137 | + twFile.getId() + " which has absolute path " + twFile.getUnschemedFilePath()); 138 | 139 | webView = v.findViewById(R.id.webview) ; 140 | webView.addJavascriptInterface(new WebAppInterface(getActivity(),twFile,webView),"twi" ); 141 | WebSettings webSettings = webView.getSettings() ; 142 | 143 | 144 | webSettings.setJavaScriptEnabled(true); 145 | webSettings.setAllowFileAccess(true); 146 | webView.getSettings().setGeolocationDatabasePath( getActivity().getFilesDir().getPath() ); 147 | webView.getSettings().setBuiltInZoomControls(true); 148 | webView.getSettings().setDisplayZoomControls(false); 149 | final MyAction titleSetter = new MyAction() { 150 | @Override 151 | void doSomethingWithValue(String useme) { 152 | //Log.d(LOG_TAG, "Inside myaction, seeing message " + useme); 153 | twFile.setTitle(useme); 154 | } 155 | } ; 156 | final MyAction iconSetter = new MyAction() { 157 | @Override 158 | void doSomethingWithValue(String useme) { 159 | Log.d(LOG_TAG, "Inside myaction iconSetter, seeing message " + useme); 160 | 161 | twFile.makeIconAndPath(getActivity(),useme); 162 | //twFile.setTitle(useme); 163 | } 164 | } ; 165 | final MyAction nullSetter = new MyAction() { 166 | @Override 167 | void doSomethingWithValue(String useme) { 168 | Log.d(LOG_TAG, "Inside myaction nullSetter, seeing message " + useme); 169 | } 170 | } ; 171 | 172 | 173 | 174 | webView.setWebViewClient(new WebViewClient() { 175 | 176 | 177 | @Override 178 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 179 | if(url.startsWith("http")) { 180 | Log.d(LOG_TAG, "Attempt to start " + url); 181 | 182 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 183 | startActivity(intent); 184 | return true; 185 | } 186 | return true ; 187 | } 188 | 189 | 190 | 191 | 192 | @Override 193 | public void onPageFinished(WebView view, String url) { 194 | //Log.d(LOG_TAG, "Inside onActivityCreated"); 195 | twFile.setTitle(view.getTitle()); 196 | //Log.d(LOG_TAG, "EXP1: Seeing title: " + view.getTitle()); 197 | //String title = loadJavascript ("$tw.wiki.getTiddlerText('$:/SiteTitle')",titleSetter); 198 | String icoText = loadJavascript ("$tw.wiki.getTiddlerText('$:/favicon.ico')",iconSetter); 199 | String message = twFile.getMessage() ; 200 | // TODO: TW-SHARE: Give clipboard unique name 201 | // TODO: TW-SHARE: Save TW-file with clipboard 202 | if(message != null && !message.isEmpty()) { 203 | message = JSONObject.quote(message); 204 | String clipTiddler = TwUtils.get(getActivity()).makeRandomizedFileName("Quinoid-Clip", ""); 205 | String command = 206 | "console.log('COMMAND 1');" + 207 | "var draftTiddler = new $tw.Tiddler( " + 208 | "{ title: \"" + clipTiddler + "\", text: " + message + "}, " + 209 | "$tw.wiki.getModificationFields()); " + 210 | "$tw.wiki.addTiddler(draftTiddler);" + 211 | "$tw.rootWidget.dispatchEvent({type: \"tm-auto-save-wiki\"}); " ; 212 | twFile.setMessage(""); // Clear message once it has been saved. 213 | //$tw.rootWidget.dispatchEvent({type: "tm-auto-save-wiki"}); 214 | Log.d(LOG_TAG, "Javascript - about to create tiddler with message: " + message); 215 | loadJavascript(command, nullSetter); 216 | } 217 | 218 | //twFile.makeIconAndPath(getActivity(),icoText); 219 | //view.getFavicon() 220 | //twFile.saveIconPath(getActivity(),view.getFavicon()); 221 | } 222 | } ); 223 | 224 | //twFile.loadFilePath(); // Need to do this in case content: hasn't been loaded 225 | String temp = "file:///"+twFile.getUnschemedFilePath() ; // This is probably what we usually call an absolute path 226 | Log.d(LOG_TAG, "About to load file " + temp); 227 | webView.loadUrl(temp); 228 | 229 | webView.setWebChromeClient(new WebChromeClient() { 230 | 231 | @Override 232 | public void onGeolocationPermissionsShowPrompt(String origin,android.webkit.GeolocationPermissions.Callback callback) { 233 | callback.invoke(origin, true, false); 234 | } 235 | 236 | 237 | //handling input[type="file"] requests for android API 16+ 238 | @SuppressLint("ObsoleteSdkInt") 239 | @SuppressWarnings("unused") 240 | public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { 241 | Log.d(LOG_TAG, "Inside file chooser method 1"); 242 | mUM = uploadMsg; 243 | Intent i = new Intent(Intent.ACTION_GET_CONTENT); 244 | i.addCategory(Intent.CATEGORY_OPENABLE); 245 | i.setType("*/*"); 246 | if (multiple_files && Build.VERSION.SDK_INT >= 18) { 247 | i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 248 | } 249 | startActivityForResult(Intent.createChooser(i, "File Chooser"), FCR); 250 | } 251 | 252 | //handling input[type="file"] requests for android API 21+ 253 | @SuppressLint("InlinedApi") 254 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { 255 | Log.d(LOG_TAG, "Inside file chooser method 2"); 256 | if (file_permission()) { 257 | Log.d(LOG_TAG, "Passed file permissions check"); 258 | String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE, 259 | Manifest.permission.READ_EXTERNAL_STORAGE, 260 | // Manifest.permission.CAMERA 261 | }; 262 | 263 | //checking for storage permission to write images for upload 264 | if (ContextCompat.checkSelfPermission(getActivity(), 265 | Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 266 | && ContextCompat.checkSelfPermission(getActivity(), 267 | Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED 268 | ) 269 | { ActivityCompat.requestPermissions(getActivity(), perms, FCR); 270 | 271 | //checking for WRITE_EXTERNAL_STORAGE permission 272 | } 273 | else if (ContextCompat.checkSelfPermission(getActivity(), 274 | Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) 275 | { 276 | ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, FCR); 277 | 278 | //checking for CAMERA permissions 279 | } 280 | // else if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 281 | // ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.CAMERA}, FCR); 282 | // } 283 | if (mUMA != null) { 284 | mUMA.onReceiveValue(null); 285 | } 286 | mUMA = filePathCallback; 287 | Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 288 | if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { 289 | File photoFile = null; 290 | try { 291 | photoFile = createImageFile(); 292 | takePictureIntent.putExtra("PhotoPath", mCM); 293 | } catch (IOException ex) { 294 | Log.e(LOG_TAG, "Image file creation failed", ex); 295 | } 296 | if (photoFile != null) { 297 | mCM = "file:" + photoFile.getAbsolutePath(); 298 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); 299 | } else { 300 | takePictureIntent = null; 301 | } 302 | } 303 | Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); 304 | contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); 305 | contentSelectionIntent.setType("*/*"); 306 | if (multiple_files) { 307 | contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 308 | } 309 | Intent[] intentArray; 310 | if (takePictureIntent != null) { 311 | intentArray = new Intent[]{takePictureIntent}; 312 | } else { 313 | intentArray = new Intent[0]; 314 | } 315 | 316 | Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); 317 | chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); 318 | chooserIntent.putExtra(Intent.EXTRA_TITLE, "File Chooser"); 319 | chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); 320 | startActivityForResult(chooserIntent, FCR); 321 | return true; 322 | }else{ 323 | Log.d(LOG_TAG, "Did NOT Pass file permissions check"); 324 | return false; 325 | } 326 | } 327 | }); 328 | 329 | 330 | 331 | 332 | //loadTWfromUri(webView, tw_file_name); 333 | 334 | return v ; 335 | } 336 | 337 | 338 | 339 | @Override 340 | public void onCreate(@Nullable Bundle savedInstanceState) { 341 | super.onCreate(savedInstanceState); 342 | 343 | } 344 | 345 | public boolean file_permission(){ 346 | // 190129 Taking out camera permission for now. 347 | if(Build.VERSION.SDK_INT >=23 && 348 | (ContextCompat.checkSelfPermission(getActivity(), 349 | Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED 350 | // || ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED 351 | )) 352 | { 353 | ActivityCompat.requestPermissions(getActivity(), 354 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE 355 | // , Manifest.permission.CAMERA 356 | } , 1); 357 | return false; 358 | }else{ 359 | return true; 360 | } 361 | } 362 | 363 | //creating new image file here 364 | private File createImageFile() throws IOException{ 365 | @SuppressLint("SimpleDateFormat") 366 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 367 | String imageFileName = "img_"+timeStamp+"_"; 368 | File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); 369 | return File.createTempFile(imageFileName,".jpg",storageDir); 370 | } 371 | 372 | 373 | 374 | 375 | 376 | @Override 377 | public void onPause() { 378 | super.onPause(); 379 | TwManager.get(getActivity()).saveTwFilesToJSON(); 380 | //Log.d(LOG_TAG, "onPause - saving to JSON complete."); 381 | } 382 | 383 | @Override 384 | public void onAttachFragment(Fragment childFragment) { 385 | super.onAttachFragment(childFragment); 386 | Log.d(LOG_TAG, "onAttachFragment"); 387 | } 388 | @Override 389 | public void onDetach() { 390 | super.onDetach(); 391 | //Log.d(LOG_TAG, "onDetach"); 392 | } 393 | @Override 394 | public void onDestroy() { 395 | super.onDestroy(); 396 | //Log.d(LOG_TAG, "onDestroy"); 397 | } 398 | 399 | 400 | /* UTILITIES */ 401 | 402 | public String loadJavascript(String javascript, final MyAction pMyAction) { 403 | //Log.d(LOG_TAG, "Receiving javascript message: " + javascript); 404 | javascriptEvalResult = "" ; 405 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 406 | // In KitKat+ you should use the evaluateJavascript method 407 | webView.evaluateJavascript(javascript, new ValueCallback() { 408 | 409 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 410 | @Override 411 | public void onReceiveValue(String s) { 412 | //Log.d(LOG_TAG, "Inside ValueCallBack seeing string: " + s); 413 | JsonReader reader = new JsonReader(new StringReader(s)); 414 | 415 | // Must set lenient to parse single values 416 | reader.setLenient(true); 417 | try { 418 | if (reader.peek() != JsonToken.NULL) { 419 | if (reader.peek() == JsonToken.STRING) { 420 | String msg = reader.nextString(); 421 | //Log.d(LOG_TAG, "I think I am returning value " + msg); 422 | if (msg != null) { 423 | pMyAction.doSomethingWithValue(msg); 424 | 425 | } 426 | } 427 | } 428 | } catch (IOException e) { 429 | Log.e("TAG", "MainActivity: IOException", e); 430 | } finally { 431 | try { 432 | reader.close(); 433 | } catch (IOException e) { 434 | // NOOP 435 | } 436 | } 437 | } 438 | }); 439 | } else { 440 | /** 441 | * For pre-KitKat+ you should use loadUrl("javascript:"); 442 | * To then call back to Java you would need to use addJavascriptInterface() 443 | * and have your JS call the interface 444 | **/ 445 | // Leave open for now. Not planning to support <4.4 446 | //mWebView.loadUrl("javascript:" + javascript); 447 | } 448 | return javascriptEvalResult ; 449 | } 450 | 451 | 452 | 453 | /* NOT USED */ 454 | public void loadTWfromUri(WebView wv, String uriString) { 455 | //GDRIVE & OTHER ANDROID 6+ 456 | Uri uriFile = Uri.parse(uriString); 457 | String sGdrive = ""; 458 | FileOutputStream outputStream; 459 | File file = null; 460 | String fileName = "huh"; 461 | // there should be more try/catch 462 | String unencodedHtml = 463 | "<!DOCTYPE><html><body>'%23' is the percent code for ‘#‘ </body></html>"; 464 | unencodedHtml = "'23' is the percent code for '#'" ; 465 | String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); 466 | //encodedHtml = "This is some sample text" ; 467 | StringBuilder html = new StringBuilder(); 468 | 469 | try { 470 | InputStream inputStream = getActivity().getContentResolver().openInputStream(uriFile); 471 | //outputStream = openFileOutput(file.getName(), MODE_PRIVATE); 472 | BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); 473 | byte[] buf = new byte[1024]; 474 | int len, cnt = 0 ; 475 | String line; 476 | while((line = br.readLine()) != null) { 477 | cnt++; 478 | html.append(line); 479 | if(cnt == 5) Log.d(LOG_TAG, html.toString()); 480 | } 481 | 482 | /* while ((len = inputStream.read(buf)) > 0) { 483 | cnt++ ; 484 | html.append(buf); 485 | if(cnt < 3) {Log.d(LOG_TAG,buf.toString())} 486 | }*/ 487 | Log.d(LOG_TAG, "End of html: " + line); 488 | //inputStream.close(); 489 | br.close(); 490 | } catch (Exception e) { 491 | Log.d(LOG_TAG, e.getMessage()); 492 | } 493 | 494 | //wv.loadData(Base64.encodeToString(html.toString().getBytes()), "text/html", "base64"); 495 | //wv.loadData(html.toString(), "text/html", "UTF-8"); 496 | //wv.loadData(html.toString() , "text/html; charset=utf-8", "UTF-8"); 497 | encodedHtml = Base64.encodeToString(html.toString().getBytes(), Base64.NO_PADDING); 498 | //wv.setLayerType(WebView.LAYER_TYPE_NONE, null); 499 | //wv.loadData(encodedHtml, "text/html", "base64"); 500 | wv.getSettings().setDefaultTextEncodingName("utf-8"); 501 | wv.getSettings().setUserAgentString(Locale.getDefault().getLanguage()); 502 | wv.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); 503 | wv.getSettings().setDomStorageEnabled(true); 504 | String root = "file://"+getActivity().getFilesDir().getAbsolutePath(); 505 | Log.d(LOG_TAG, "root: " + root); 506 | //wv.setWebViewClient(new MyWebViewClient()); 507 | 508 | wv.loadDataWithBaseURL(root, html.toString(), "text/html", "UTF-8", root); 509 | //wv.loadDataWithBaseURL("", encodedHtml, "text/html", "base64", ""); 510 | } // LOAD TW FROM URI 511 | 512 | /* NOT USED ?? */ 513 | private class MyWebViewClient extends WebViewClient { 514 | @Override 515 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 516 | // Let's try overriding everything 517 | Log.d(LOG_TAG, "Attempt to use resource " + url); 518 | if(url.startsWith("http")) { 519 | Log.d(LOG_TAG, "Attempt to start " + url); 520 | 521 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 522 | startActivity(intent); 523 | return true; 524 | } 525 | return true; 526 | } 527 | 528 | @TargetApi(21) 529 | @Override 530 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 531 | //Log.d(LOG_TAG,request.getUrl().toString()) ; 532 | return null ; 533 | } 534 | } 535 | 536 | 537 | 538 | 539 | 540 | } 541 | 542 | abstract class MyAction{ 543 | void doSomethingWithValue(String useme) { 544 | 545 | } 546 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | import android.view.Window; 10 | 11 | public class TwFragmentActivity extends AppCompatActivity { 12 | static final public String LOG_TAG = "32XND-TFA" ; 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | // requestWindowFeature(Window.FEATURE_NO_TITLE); 17 | setContentView(R.layout.tw_fragment_activity); 18 | Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragmentContainer) ; 19 | Intent intent = getIntent(); 20 | String sFilename = intent.getStringExtra(TwFragment.TW_FILE_NAME); 21 | if (fragment == null) { 22 | fragment = TwFragment.newInstance(sFilename) ; 23 | getSupportFragmentManager().beginTransaction() 24 | .add(R.id.fragmentContainer, fragment ) 25 | .commitNow(); 26 | } 27 | } 28 | 29 | @Override 30 | public void onAttachFragment(Fragment fragment) { 31 | super.onAttachFragment(fragment); 32 | //Log.d(LOG_TAG, "onAttachFragment"); 33 | } 34 | 35 | @Override 36 | protected void onDestroy() { 37 | super.onDestroy(); 38 | //Log.d(LOG_TAG, "onDestroy"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwJSONSerializer.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONTokener; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.File; 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.io.OutputStream; 17 | import java.io.OutputStreamWriter; 18 | import java.io.Writer; 19 | import java.util.ArrayList; 20 | 21 | public class TwJSONSerializer { 22 | private Context mContext; 23 | private String mFilename ; 24 | final public String SERIALIZER_DIR = "persist"; 25 | final public String LOG_TAG = "TwJSONSerializer"; 26 | 27 | public TwJSONSerializer(Context c, String s) { 28 | mContext = c ; 29 | mFilename = s ; 30 | } 31 | 32 | public void saveTwFiles(ArrayList twFiles) throws JSONException, IOException { 33 | JSONArray array = new JSONArray() ; 34 | Log.d(LOG_TAG, "saveTwFiles: Loading array...") ; 35 | for (TwFile tw : twFiles) { 36 | array.put(tw.toJSON()) ; 37 | } 38 | Log.d(LOG_TAG, "saveTwFiles: Finished loading array..."); 39 | 40 | // Write to internal storage 41 | // This first draft doesn't allow specifying directory. Problem? 42 | Writer writer = null ; 43 | try { 44 | OutputStream out = mContext.openFileOutput(mFilename, Context.MODE_PRIVATE); 45 | writer = new OutputStreamWriter(out); 46 | writer.write(array.toString()); 47 | 48 | } finally { 49 | if(writer!= null) writer.close(); 50 | } 51 | } 52 | 53 | public ArrayList loadTwFilesFromJSON(Context c) throws IOException, JSONException { 54 | ArrayList twFiles = new ArrayList() ; 55 | BufferedReader reader = null ; 56 | try { 57 | InputStream in = mContext.openFileInput(mFilename) ; 58 | reader = new BufferedReader(new InputStreamReader(in)); 59 | StringBuilder sb = new StringBuilder(); 60 | String jsonString = null ; 61 | while ((jsonString = reader.readLine()) !=null ) { 62 | sb.append(jsonString); 63 | } 64 | JSONArray array = (JSONArray) new JSONTokener(sb.toString()).nextValue() ; 65 | for(int i=0;i mTwFiles; 24 | private boolean mDirIsClean; 25 | private TwJSONSerializer mSerializer ; 26 | 27 | 28 | // public void loadTwFilesFromPreferences() { 29 | // 30 | // 31 | // mTwFiles.clear(); 32 | // Set stringDefaults = new HashSet(); 33 | // // See if there is a set of TW files to import 34 | // Set stringSet = PreferenceManager.getDefaultSharedPreferences(mAppContext) 35 | // .getStringSet(mAppContext.getString(R.string.preferences_string), stringDefaults); 36 | // int cnt = 0; 37 | // for (String s : stringSet) { 38 | // mTwFiles.add(new TwFile(mAppContext, s)); 39 | // cnt++; 40 | // } 41 | // //Log.d(LOG_TAG, "I loaded " + cnt + " files."); 42 | // } 43 | 44 | 45 | 46 | // public void saveTwFilesToPreferences() { 47 | // 48 | // Set stringSet = new HashSet(); 49 | // for (TwFile s : mTwFiles) { 50 | // stringSet.add(s.getId()); 51 | // } 52 | // // See if there is a set of TW files to import 53 | // PreferenceManager.getDefaultSharedPreferences(mAppContext).edit().putStringSet(mAppContext.getString(R.string.preferences_string), stringSet).commit(); 54 | // 55 | // } 56 | 57 | public boolean saveTwFilesToJSON() { 58 | try { 59 | mSerializer.saveTwFiles(mTwFiles); 60 | //Log.d(LOG_TAG, "saveTwFiles() - Files saved to JSON, hopefully"); 61 | return true; 62 | } catch (Exception e) { 63 | Log.d(LOG_TAG,"saveTwFilesToJSON() - ERR in saving to JSON: "+ e.getMessage()); 64 | return false; 65 | } 66 | } 67 | 68 | 69 | private TwManager(Context app) { 70 | mAppContext = app; 71 | 72 | TwManager.cleanTempDir(app); 73 | mSerializer = new TwJSONSerializer(mAppContext, FILENAME); 74 | 75 | try { 76 | mTwFiles = mSerializer.loadTwFilesFromJSON(app) ; 77 | //Log.d(LOG_TAG, "Constructor: Files loaded."); 78 | } catch(Exception e) { 79 | mTwFiles = new ArrayList(); 80 | Log.d(LOG_TAG, "ERR loading TwFiles", e); 81 | } 82 | 83 | // Check if there are any new files in public dire 84 | addFromDocumentDir(app); 85 | 86 | //mDirIsClean = false ; 87 | /* 88 | for (int i = 0; i < 100; i++) { 89 | TwFile c = new TwFile(); 90 | c.setTitle("File #" + i); 91 | mTwFiles.add(c); 92 | } 93 | */ 94 | } 95 | 96 | public ArrayList getTwFiles() { 97 | return mTwFiles; 98 | } 99 | 100 | 101 | public void addTwFile(TwFile c) { 102 | for (TwFile x : mTwFiles) { 103 | if (c.getId().equals(x.getId())) { 104 | //Log.d(LOG_TAG, "I think I see file " + c.getId() + " and am exiting."); 105 | return; 106 | } 107 | } 108 | //Log.d(LOG_TAG, "Adding file " + c.getId()); 109 | mTwFiles.add(c); 110 | //saveCrimes(); 111 | } 112 | 113 | public TwFile getTwFile(String id) { 114 | //Log.d(LOG_TAG, "Getting passed file: " + id); 115 | 116 | for (TwFile x : mTwFiles) { 117 | if (id.equals(x.getId())) { 118 | //Log.d(LOG_TAG, "getTwFile: I think I see file " + id + " and am returning it."); 119 | return x; 120 | } 121 | } 122 | return null; 123 | 124 | } 125 | 126 | public ArrayList getBrowsableFiles() { 127 | //Log.d(LOG_TAG, "Getting passed file: " + id); 128 | ArrayList browsables = new ArrayList(); 129 | for (TwFile x : mTwFiles) { 130 | if (x.isBrowsable()) { 131 | browsables.add(x); 132 | } 133 | } 134 | return browsables; 135 | 136 | } 137 | 138 | 139 | public TwFile getTwFile(int index) { 140 | return mTwFiles.get(index); 141 | } 142 | 143 | 144 | public void deleteTwFile(TwFile c) { 145 | mTwFiles.remove(c); 146 | //saveCrimes(); 147 | } 148 | 149 | public void setAsClipboard(TwFile c) { 150 | for (TwFile x : mTwFiles) { 151 | if (x.isClipboard()) { 152 | x.setIsClipboard(false); 153 | } 154 | } 155 | 156 | c.setIsClipboard(true); 157 | //saveCrimes(); 158 | } 159 | 160 | public int getClipboardIndex() { 161 | int i = 0; 162 | for (TwFile x : mTwFiles) { 163 | if (x.isClipboard()) { 164 | break; 165 | } 166 | i++ ; 167 | } 168 | if(i == mTwFiles.size() ) return 0 ; // If there are no clipboards, use the first TW file 169 | return i ; 170 | } 171 | 172 | public void deleteTwFile(int i) { 173 | mTwFiles.remove(i); 174 | } 175 | 176 | public static TwManager get(Context c) { 177 | if (sTwManager == null) { 178 | sTwManager = new TwManager(c.getApplicationContext()); 179 | 180 | } 181 | return sTwManager; 182 | } 183 | 184 | private static void cleanTempDir(Context cntx) { 185 | File tempFile = cntx.getDir("provided", Context.MODE_PRIVATE).getAbsoluteFile(); 186 | for (File reallyTempFile : tempFile.listFiles()) { 187 | //Log.d(LOG_TAG, "About to delete file " + reallyTempFile.getName()); 188 | if (reallyTempFile.isFile()) reallyTempFile.delete(); 189 | } 190 | } 191 | 192 | public void addFromDocumentDir(Context cntx) { 193 | 194 | if(!TwActivity.ReadWritePermissionGranted) { 195 | if (ContextCompat.checkSelfPermission(cntx, 196 | Manifest.permission.WRITE_EXTERNAL_STORAGE) 197 | != PackageManager.PERMISSION_GRANTED) { 198 | Log.d(LOG_TAG, "It thinks it needs write permission."); 199 | 200 | TwUtils.get(cntx).makeToast("Do not have permission to load from ext. directory."); 201 | return; 202 | 203 | } 204 | 205 | } 206 | 207 | 208 | File tempFile = TwUtils.get(cntx).getTWDocumentPath(TwActivity.TW_SUBDIR) ; 209 | Log.d(LOG_TAG, "Checking directory: " + tempFile.getAbsolutePath()); 210 | //File tempFile = cntx.getDir("provided", Context.MODE_PRIVATE).getAbsoluteFile(); 211 | ArrayList arrayList = new ArrayList(); 212 | 213 | 214 | 215 | 216 | //ArrayList lTwFiles = TwManager.get(cntx).getTwFiles() ; 217 | for (TwFile tf : mTwFiles) { 218 | arrayList.add(tf.getUnschemedFilePath()) ; 219 | // Log.d(LOG_TAG,"addFromDocumentDir: I see saved file " + tf.getUnschemedFilePath()) ; 220 | // if(arrayList.contains(tf.getUnschemedFilePath())) 221 | // Log.d(LOG_TAG, "I think it has been added: " + tf.getUnschemedFilePath()); 222 | } 223 | 224 | for (File realFile : tempFile.listFiles()) { 225 | if(realFile.getName().endsWith(".json")) continue; 226 | if(arrayList.contains(realFile.getAbsoluteFile().toString())) 227 | Log.d(LOG_TAG, "addFromdocumentDir: I HAVE FOUND:" + realFile.getAbsoluteFile() + "XXX"); 228 | else 229 | 230 | { 231 | Log.d(LOG_TAG, "addFromdocumentDir: NOT -- FOUND:" + realFile.getAbsoluteFile() + "XXX"); 232 | TwFile twFile = new TwFile(cntx, realFile.getAbsoluteFile().toString()); 233 | addTwFile(twFile); 234 | } 235 | // Log.d(LOG_TAG, "About to check for file " + reallyTempFile.getName()); 236 | //if (reallyTempFile.isFile()) reallyTempFile.delete(); 237 | 238 | } 239 | 240 | } 241 | 242 | 243 | 244 | public boolean anyFileBased() { 245 | int cnt = 0; 246 | for (TwFile x : mTwFiles) { 247 | if (! x.isIsContentType()) cnt++ ; 248 | } 249 | //Log.d(LOG_TAG, "Just counted " + cnt + " file-type files"); 250 | if(mTwFiles.size()>0 && cnt > 0 ) return true ; 251 | return false ; 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwPagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentStatePagerAdapter; 7 | import android.support.v4.view.ViewPager; 8 | import android.util.Log; 9 | import android.widget.ArrayAdapter; 10 | 11 | import java.util.ArrayList; 12 | 13 | public class TwPagerActivity extends FragmentActivity { 14 | static String LOG_TAG = "32XND-TwPagerActivity"; 15 | private ViewPager mViewPager; 16 | 17 | private ArrayList mTwBrowsableFiles ; 18 | @Override 19 | public void onCreate(Bundle savedlnstanceState) { 20 | super.onCreate(savedlnstanceState); 21 | 22 | int position = getIntent().getIntExtra(TwActivity.LAUNCH_PAGE,0) ; 23 | 24 | // Fragment Manager Code 25 | // mViewPager = new ViewPager(this); 26 | // mViewPager.setId(R.id.viewPager); 27 | 28 | 29 | //setContentView (mViewPager) ; 30 | setContentView(R.layout.tw_pager_view); 31 | mViewPager = findViewById(R.id.viewpager) ; 32 | 33 | mTwBrowsableFiles = TwManager.get(this).getBrowsableFiles() ; 34 | FragmentManager fm = getSupportFragmentManager(); 35 | mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) { 36 | @Override 37 | public int getCount() { 38 | return mTwBrowsableFiles.size(); 39 | } 40 | 41 | @Override 42 | public TwFragment getItem(int pos) { 43 | TwFile twFile = mTwBrowsableFiles.get(pos); 44 | return TwFragment.newInstance(twFile.getId()); 45 | } 46 | }); 47 | 48 | mViewPager.setCurrentItem(position); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwResource.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.util.Log; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | import org.json.JSONTokener; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.FileNotFoundException; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.InputStreamReader; 17 | import java.util.ArrayList; 18 | 19 | public class TwResource { 20 | public static final String LOG_TAG = "32XND-TwResource"; 21 | public static final String DEFAULT_TITLE = "(Unspecified Resource)"; 22 | private static final String JSON_ID = "id"; 23 | private static final String JSON_TITLE = "title"; 24 | private static final String JSON_DESCRIPTION = "description"; 25 | private static final String JSON_FILESTEM = "filestem"; 26 | private static final String DEFAULT_DESCRIPTION = "No description provided"; 27 | 28 | public static ArrayList mTwResources = new ArrayList(); 29 | 30 | static { 31 | String resourceFile = (new File(TwActivity.sTwUtils.getTWDocumentPath(TwActivity.TW_SUBDIR),TwActivity.RESOURCE_LIST_FILE)).toString() ; 32 | Log.d(LOG_TAG, "I see resource file name: " + resourceFile); 33 | 34 | mTwResources = TwResource.loadTwResourceFromJSON(resourceFile); 35 | 36 | if(mTwResources == null || mTwResources.isEmpty()) { 37 | 38 | Log.d(LOG_TAG, "Resources empty!"); 39 | } 40 | Log.d(LOG_TAG, "Resources retrieved, maybe"); 41 | 42 | // TwResource tempResource = mTwResources.get(1); 43 | // if(tempResource != null) 44 | // Log.d(LOG_TAG, "Resource title is: " + tempResource.getTitle()); 45 | 46 | } 47 | //DONE - SAVE JSON for CLIPBOARD 48 | //DONE - Make methods in TW Manager to select and set just one TW as clipboard 49 | 50 | private String mTitle = DEFAULT_TITLE; 51 | private String mId; 52 | // Need to provide a file stem for the downloader to use, since the URL may not actually be in a good form 53 | private String mFileStem; 54 | 55 | public String getDescription() { 56 | return mDescription; 57 | } 58 | 59 | public void setDescription(String mDescription) { 60 | this.mDescription = mDescription; 61 | } 62 | 63 | private String mDescription ; 64 | 65 | public String getFileStem() { 66 | return mFileStem; 67 | } 68 | 69 | public void setFileStem(String mFileName) { 70 | this.mFileStem = mFileName; 71 | } 72 | 73 | 74 | 75 | // This is the path to the physical file that we are using in WebView 76 | // but without the file:/// part because we need that to create streams 77 | // for saving. WebView requires a path with "file:///" in it. 78 | private String unschemedFilePath; // Set when loading 79 | 80 | 81 | // Constructor #2 82 | public TwResource(JSONObject json) throws JSONException { 83 | mId = json.getString(JSON_ID); 84 | mTitle = json.getString(JSON_TITLE); 85 | mDescription = json.getString(JSON_DESCRIPTION) ; 86 | mFileStem = json.getString(JSON_FILESTEM); 87 | //loadFilePath(c); 88 | } 89 | 90 | // public void loadFilePath(Context c) { 91 | // String newFilePath = "IF YOU CAN READ THIS SOMETHING WENT WRONG."; 92 | // // We only need to sources that are content types. At least so far 93 | // 94 | // try { 95 | // //Log.d(LOG_TAG, "Trying to create file name."); 96 | // newFilePath = File.createTempFile("TIDDLYWIKISTUB", ".html", 97 | // c.getDir("provided", Context.MODE_PRIVATE)).getAbsolutePath() ; 98 | // //Log.d(LOG_TAG, "Created file name: " + newFilePath); 99 | // int cnt = 0 ; // For DEBUG 100 | // setUnschemedFilePath(newFilePath); 101 | // Uri uriFile = Uri.parse(mId); 102 | // InputStream inputStream = c.getContentResolver().openInputStream(uriFile); 103 | // OutputStream outputStream = new FileOutputStream(newFilePath); 104 | // 105 | // byte[] buf = new byte[1024]; 106 | // int len; 107 | // while ((len = inputStream.read(buf)) > 0) { 108 | // cnt++ ; 109 | // outputStream.write(buf, 0, len); 110 | // } 111 | // 112 | // outputStream.flush(); 113 | // outputStream.close(); 114 | // inputStream.close(); 115 | // //Log.d(LOG_TAG, "I counted " + cnt + "k bytes"); 116 | // } catch (IOException e) { 117 | // Log.d(LOG_TAG, "IOException: " + e.getMessage()); 118 | // e.printStackTrace(); 119 | // 120 | // } catch (Exception e) { 121 | // Log.e(LOG_TAG, "Something not an IOE happened: " + e.getMessage()); 122 | // } 123 | // //Log.d(LOG_TAG, "I think I've successfully initialzed working file " + getUnschemedFilePath()); 124 | // 125 | // } 126 | 127 | // public void saveFile(String text,Context c) { 128 | // 129 | // try { 130 | // //Log.d(LOG_TAG, "Attempting to stream to: " + getUnschemedFilePath()); 131 | // FileOutputStream outputStream = new FileOutputStream(getUnschemedFilePath()); 132 | // outputStream.write(text.getBytes()); 133 | // outputStream.close(); 134 | // } catch (Exception e) { 135 | // //Log.d(LOG_TAG, "IOE?: See stacktrace." + e.getMessage()); 136 | // e.printStackTrace(); 137 | // } 138 | // 139 | // if (mIsContentType) { 140 | // try { 141 | // OutputStream outputStream = c.getContentResolver().openOutputStream(Uri.parse(mId)); 142 | // //OutputStream = new FileOutputStream(newFilePath); 143 | // Writer writer = new OutputStreamWriter(outputStream, "UTF-8"); 144 | // writer.write(text); 145 | // writer.close(); 146 | // } catch (IOException e) { 147 | // Log.d(LOG_TAG, "saveFile ERR trying to write to original system file."); 148 | // } 149 | // } 150 | // } 151 | 152 | public void setUnschemedFilePath(String filePath) { 153 | Log.d(LOG_TAG, "719X Before setting file path: " + filePath); 154 | filePath = filePath.replaceFirst("^file:/+","/") ; 155 | Log.d(LOG_TAG, "719X After setting file path: " + filePath); 156 | this.unschemedFilePath = filePath; 157 | } 158 | 159 | public String getUnschemedFilePath() { 160 | // Maybe this is same as absolute path. It's the path to the WORKING file which may be a temp file. 161 | return this.unschemedFilePath; 162 | } 163 | 164 | public String getTitle() { 165 | return mTitle; 166 | } 167 | public void setTitle(String pTitle) { 168 | // if(mTitle == null || mTitle.equals(DEFAULT_TITLE)) || mTitle.equals(pTitle){ 169 | //Log.d(LOG_TAG, "Setting mTitle to: " + pTitle); 170 | this.mTitle = pTitle; 171 | // } 172 | } 173 | 174 | 175 | public String getId() { 176 | return mId; 177 | } 178 | 179 | public void setId(String id) { 180 | //Log.d(LOG_TAG, "Setting mId to: " + id); 181 | this.mId = id; 182 | } 183 | 184 | 185 | 186 | 187 | @Override 188 | public String toString() { 189 | return mId; 190 | } 191 | 192 | public JSONObject toJSON() throws JSONException { 193 | JSONObject json = new JSONObject(); 194 | json.put(JSON_ID, mId.toString()); 195 | json.put(JSON_TITLE, mTitle.toString()); 196 | json.put(JSON_DESCRIPTION, mDescription.toString()); 197 | json.put(JSON_FILESTEM, mFileStem.toString()); 198 | return json; 199 | 200 | 201 | } 202 | 203 | 204 | public static ArrayList loadTwResourceFromJSON(String pFilename) { 205 | ArrayList fileArrayList = new ArrayList() ; 206 | BufferedReader reader = null ; 207 | try { 208 | InputStream in = new FileInputStream(pFilename) ; 209 | reader = new BufferedReader(new InputStreamReader(in)); 210 | StringBuilder sb = new StringBuilder(); 211 | String jsonString = null ; 212 | while ((jsonString = reader.readLine()) !=null ) { 213 | sb.append(jsonString); 214 | } 215 | Log.d(LOG_TAG, "I see JSON string: " + sb.toString()); 216 | JSONArray array = (JSONArray) new JSONTokener(sb.toString()).nextValue() ; 217 | for(int i=0;i 21 | * Activities containing this fragment MUST implement the {@link OnListFragmentInteractionListener} 22 | * interface. 23 | */ 24 | public class TwResourceFragment extends Fragment { 25 | 26 | // TODO: Customize parameter argument names 27 | private static final String ARG_COLUMN_COUNT = "column-count"; 28 | // TODO: Customize parameters 29 | private int mColumnCount = 1; 30 | private OnListFragmentInteractionListener mListener; 31 | public static final String LOG_TAG = "32X TwResourceFragment"; 32 | 33 | /** 34 | * Mandatory empty constructor for the fragment manager to instantiate the 35 | * fragment (e.g. upon screen orientation changes). 36 | */ 37 | public TwResourceFragment() { 38 | } 39 | 40 | // TODO: Customize parameter initialization 41 | @SuppressWarnings("unused") 42 | public static TwResourceFragment newInstance(int columnCount) { 43 | TwResourceFragment fragment = new TwResourceFragment(); 44 | Bundle args = new Bundle(); 45 | args.putInt(ARG_COLUMN_COUNT, columnCount); 46 | fragment.setArguments(args); 47 | return fragment; 48 | } 49 | 50 | @Override 51 | public void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | 54 | if (getArguments() != null) { 55 | mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT); 56 | } 57 | } 58 | 59 | @Override 60 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 61 | Bundle savedInstanceState) { 62 | View view = inflater.inflate(R.layout.fragment_twresource_list, container, false); 63 | Log.d(LOG_TAG, "Inside onCreateView."); 64 | // Set the adapter 65 | if (view instanceof RecyclerView) { 66 | Log.d(LOG_TAG, "onCreateView - I see instanceof recyclcerView"); 67 | Context context = view.getContext(); 68 | RecyclerView recyclerView = (RecyclerView) view; 69 | // if (mColumnCount <= 1) { 70 | LinearLayoutManager layoutManager = new LinearLayoutManager(context); 71 | recyclerView.setLayoutManager(layoutManager); 72 | // recyclerView.setLayoutManager(new LinearLayoutManager(context)); 73 | // } else { 74 | // recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount)); 75 | // } 76 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), 77 | layoutManager.getOrientation()); 78 | recyclerView.addItemDecoration(dividerItemDecoration); 79 | 80 | recyclerView.setAdapter(new TwResourceRecyclerViewAdapter(TwResource.mTwResources, mListener)); 81 | 82 | } 83 | 84 | 85 | 86 | return view; 87 | } 88 | 89 | 90 | @Override 91 | public void onAttach(Context context) { 92 | super.onAttach(context); 93 | if (context instanceof OnListFragmentInteractionListener) { 94 | mListener = (OnListFragmentInteractionListener) context; 95 | } else { 96 | throw new RuntimeException(context.toString() 97 | + " must implement OnListFragmentInteractionListener"); 98 | } 99 | } 100 | 101 | @Override 102 | public void onDetach() { 103 | super.onDetach(); 104 | mListener = null; 105 | } 106 | 107 | /** 108 | * This interface must be implemented by activities that contain this 109 | * fragment to allow an interaction in this fragment to be communicated 110 | * to the activity and potentially other fragments contained in that 111 | * activity. 112 | *

113 | * See the Android Training lesson Communicating with Other Fragments for more information. 116 | */ 117 | public interface OnListFragmentInteractionListener { 118 | 119 | void onListFragmentInteraction(TwResource twResource); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwResourceRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.example.my32ndapplication.TwResourceFragment.OnListFragmentInteractionListener; 11 | //import com.example.my32ndapplication.dummy.DummyContent.DummyItem; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * {@link RecyclerView.Adapter} that can display a {@link DummyItem} and makes a call to the 18 | * specified {@link OnListFragmentInteractionListener}. 19 | * TODO: Replace the implementation with code for your data type. 20 | */ 21 | public class TwResourceRecyclerViewAdapter extends RecyclerView.Adapter { 22 | 23 | private final ArrayList mTwResources; 24 | private final OnListFragmentInteractionListener mListener; 25 | public static final String LOG_TAG = "32X TwResRecyclVuAdaptr"; 26 | public TwResourceRecyclerViewAdapter(ArrayList items, OnListFragmentInteractionListener listener) { 27 | mTwResources = items; 28 | mListener = listener; 29 | Log.d(LOG_TAG, "adapter - I see # of items:" + items.size()); 30 | 31 | } 32 | 33 | @Override 34 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 35 | View view = LayoutInflater.from(parent.getContext()) 36 | .inflate(R.layout.fragment_twresource, parent, false); 37 | return new ViewHolder(view); 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(final ViewHolder holder, int position) { 42 | Log.d(LOG_TAG, "onBindViewHolder. See pos # " + position); 43 | holder.mTwResource = mTwResources.get(position); 44 | holder.mTitleView.setText(mTwResources.get(position).getTitle()); 45 | holder.mDescriptionView.setText(mTwResources.get(position).getDescription()); 46 | 47 | holder.mView.setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | if (null != mListener) { 51 | // Notify the active callbacks interface (the activity, if the 52 | // fragment is attached to one) that an item has been selected. 53 | mListener.onListFragmentInteraction(holder.mTwResource); 54 | } 55 | } 56 | }); 57 | } 58 | 59 | 60 | 61 | 62 | @Override 63 | public int getItemCount() { 64 | return mTwResources.size(); 65 | } 66 | 67 | public class ViewHolder extends RecyclerView.ViewHolder { 68 | public final View mView; 69 | //public final TextView mIdView; 70 | public final TextView mTitleView; 71 | public final TextView mDescriptionView; 72 | public TwResource mTwResource; 73 | 74 | public ViewHolder(View view) { 75 | super(view); 76 | mView = view; 77 | // The url is the id, and we don't display that 78 | //mIdView = (TextView) view.findViewById(R.id.item_number); 79 | mTitleView = (TextView) view.findViewById(R.id.tw_resource_title); 80 | mDescriptionView = (TextView) view.findViewById(R.id.tw_resource_description); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return super.toString() + " '" + mTitleView.getText() + "'"; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/TwUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.app.DownloadManager; 4 | import android.content.Context; 5 | import android.content.res.AssetManager; 6 | import android.net.ConnectivityManager; 7 | import android.net.NetworkInfo; 8 | import android.net.Uri; 9 | import android.os.Environment; 10 | import android.util.Log; 11 | import android.widget.Toast; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.text.SimpleDateFormat; 19 | import java.util.Arrays; 20 | import java.util.Date; 21 | import java.util.UUID; 22 | 23 | import static android.content.Context.DOWNLOAD_SERVICE; 24 | 25 | public class TwUtils { 26 | 27 | static TwUtils sTwUtils; 28 | static Context mAppContext ; 29 | static final String LOG_TAG = "32XND.TwUtils:"; 30 | 31 | public static TwUtils get(Context c) { 32 | if (sTwUtils == null) { 33 | sTwUtils = new TwUtils(c.getApplicationContext()); 34 | 35 | } 36 | return sTwUtils; 37 | } 38 | 39 | private TwUtils(Context app) { 40 | mAppContext = app; 41 | } 42 | 43 | 44 | public void makeToast(String tm) { 45 | Toast.makeText(mAppContext, tm, Toast.LENGTH_SHORT).show(); 46 | } 47 | 48 | public String getTWExternalStoragePublicDirPathname() { 49 | String publicPathStr; 50 | try { 51 | publicPathStr = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS). 52 | getCanonicalPath(); 53 | } catch (IOException e) { 54 | return null ; 55 | } 56 | return publicPathStr+ "/TW-Files/" ; 57 | 58 | } 59 | 60 | 61 | public File getTWDocumentPath(String subdir) { 62 | File file ; 63 | file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),subdir); 64 | 65 | if (!file.mkdirs()) { 66 | Log.d(LOG_TAG, "Directory for " + subdir + " not created."); 67 | } 68 | Log.d(LOG_TAG, "Returning path: " + file.getAbsolutePath()); 69 | return file ; 70 | } 71 | 72 | 73 | 74 | public String makeRandomizedFileName(String filename, String suffix) { 75 | String formattedDate = new SimpleDateFormat("yyyyMMdd-HH-mm-ss").format(new Date()); 76 | return filename + "-" + formattedDate + suffix ; 77 | } 78 | 79 | 80 | 81 | 82 | /* Checks if external storage is available for read and write */ 83 | public boolean isExternalStorageWritable() { 84 | String publicPathStr = getTWExternalStoragePublicDirPathname(); 85 | 86 | Log.d(LOG_TAG, "I am testing path: " + publicPathStr); 87 | 88 | if (publicPathStr == null) return false ; 89 | File publicFilePath = new File(publicPathStr); 90 | //File file = new File(, "TW-Files/empty1.html"); 91 | // if (Environment.MEDIA_MOUNTED.equals(state)) { 92 | // return true; 93 | // } 94 | publicFilePath.mkdirs() ; 95 | if (publicFilePath.exists() ) return true ; 96 | return false; 97 | } 98 | 99 | 100 | static boolean isConnected(Context c) { 101 | boolean isConnected = false; 102 | ConnectivityManager cm = 103 | (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); 104 | 105 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 106 | isConnected = activeNetwork != null && 107 | activeNetwork.isConnectedOrConnecting(); 108 | return isConnected; 109 | } 110 | 111 | boolean fileExistsInDocuments(String pFilename) { 112 | boolean result = false ; 113 | File f = new File(pFilename); 114 | String name = f.getName() ; 115 | File target = new File(getTWDocumentPath(TwActivity.TW_SUBDIR),name) ; 116 | if(target.exists() && !target.isDirectory()) { 117 | Log.d(LOG_TAG, "I think asset file already exists: " + pFilename); 118 | result = true ; 119 | } 120 | return result ; 121 | } 122 | 123 | // Will do a "safe" copy (no overwrite) to start 124 | void copySpecificAssets() { 125 | 126 | String[] myAssets = {"TwResources.json"} ; 127 | 128 | Log.d(LOG_TAG, "Asset copier being called"); 129 | AssetManager assetManager = mAppContext.getAssets() ; 130 | String[] files = null; 131 | try { 132 | files = assetManager.list(""); 133 | } catch (IOException e) { 134 | Log.d(LOG_TAG, "Failed to get asset file list.", e); 135 | } 136 | 137 | 138 | 139 | if (files != null) for (String filename : files) { 140 | 141 | 142 | if(!Arrays.asList(myAssets).contains(filename)) { 143 | Log.d(LOG_TAG, "Didn't find " + filename + " in " + myAssets ); 144 | return; 145 | } 146 | 147 | Log.d(LOG_TAG, "Looking for asset: " + filename); 148 | InputStream in = null; 149 | OutputStream out = null; 150 | if(fileExistsInDocuments(filename)) continue; // In the future we may want to overwrite existing files. 151 | //if((new File(filename)).isDirectory()) continue; // For the moment, skip sub-directories 152 | // if(!(new File(filename)).exists()) { 153 | // Log.d(LOG_TAG, "I think asset doesn't exist??: " + filename); 154 | // continue ; } // Some things that get listed don't actually exist ?? 155 | try { 156 | in = assetManager.open(filename); 157 | File outFile = new File(getTWDocumentPath(TwActivity.TW_SUBDIR), filename); 158 | out = new FileOutputStream(outFile); 159 | copyFile(in, out); 160 | } catch(IOException e) { 161 | Log.d(LOG_TAG, "IOE - Failed to copy asset file: " + filename, e); 162 | } 163 | finally { 164 | if (in != null) { 165 | try { 166 | in.close(); 167 | } catch (IOException e) { 168 | // NOOP 169 | } 170 | } 171 | if (out != null) { 172 | try { 173 | out.close(); 174 | } catch (IOException e) { 175 | // NOOP 176 | } 177 | } 178 | } 179 | } 180 | } 181 | void copyFile(InputStream in, OutputStream out) throws IOException { 182 | byte[] buffer = new byte[1024]; 183 | int read; 184 | while((read = in.read(buffer)) != -1){ 185 | out.write(buffer, 0, read); 186 | } 187 | } 188 | 189 | public long DownloadTw(String pUrl,File pFilePath, String pFileName ,String pDescription) { 190 | 191 | Uri uri = Uri.parse(pUrl); 192 | 193 | long downloadReference; 194 | 195 | // Downloading to internal instead. 196 | // // Make isExternalStorageWritable part of the getTWExternalStoragePublicDirPathname logic 197 | // if (! sTwUtils.isExternalStorageWritable()) { 198 | // sTwUtils.makeToast("This operation requires external storage."); 199 | // return 0 ; 200 | // } else { 201 | // Log.d(LOG_TAG, "Apparently I think there is external storage."); 202 | // } 203 | 204 | 205 | // String twFilePath = sTwUtils.getTWExternalStoragePublicDirPathname() ; 206 | // if ( twFilePath == null ) { 207 | // sTwUtils.makeToast("Not able to obtain external public storage."); 208 | // return REFERENCE_UNAVAILABLE ; 209 | // } 210 | // 211 | // twFilePath = twFilePath + "/" + sTwUtils.makeRandomizedFileName(pFileStem, ".html"); 212 | 213 | 214 | // Create request for android download manager 215 | //DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); 216 | DownloadManager downloadManager = (DownloadManager) mAppContext.getSystemService(DOWNLOAD_SERVICE) ; 217 | DownloadManager.Request request = new DownloadManager.Request(uri); 218 | 219 | //Setting title of request 220 | request.setTitle("Download file" ); 221 | 222 | //Setting description of request 223 | request.setDescription("Download: " + pDescription); 224 | 225 | //Set the local destination for the downloaded file to a path within the application's external files directory 226 | // if (v.getId() == R.id.DownloadMusic) 227 | // request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "AndroidTutorialPoint.mp3"); 228 | // else if (v.getId() == R.id.DownloadImage) 229 | // request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "AndroidTutorialPoint.jpg"); 230 | 231 | //request.setDestinationInExternalFilesDir(TwActivity.this, Environment.DIRECTORY_DOWNLOADS, "TW-test.html"); 232 | //File file = new File(TwUtils.get(this).getTWExternalStoragePublicDirPathname(), "TW-test.html"); 233 | File file = new File(pFilePath,pFileName); 234 | //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "TW-test.html"); 235 | Log.d(LOG_TAG, "I think requesting this file path: " + file.getPath()); 236 | Log.d(LOG_TAG, "I think requesting this file : " + file.getName() ); 237 | 238 | 239 | request.setDestinationUri(Uri.fromFile(file)); 240 | //Enqueue download and save into referenceId 241 | downloadReference = downloadManager.enqueue(request); 242 | 243 | //TwFile twFile = new TwFile(this, file.getPath()); 244 | TwFile twFile = new TwFile(mAppContext, file.getPath()); 245 | 246 | twFile.setTitle(pDescription); 247 | TwActivity.sTwDownloads.put(downloadReference, twFile); 248 | 249 | 250 | // Button DownloadStatus = (Button) findViewById(R.id.DoSwnloadStatus); 251 | // DownloadStatus.setEnabled(true); 252 | // Button CancelDownload = (Button) findViewById(R.id.CancelDownload); 253 | // CancelDownload.setEnabled(true); 254 | 255 | return downloadReference; 256 | } 257 | 258 | 259 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/my32ndapplication/WebAppInterface.java: -------------------------------------------------------------------------------- 1 | package com.example.my32ndapplication; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.webkit.JavascriptInterface; 6 | import android.webkit.WebView; 7 | import android.widget.Toast; 8 | 9 | import java.io.FileOutputStream; 10 | 11 | public class WebAppInterface { 12 | Context mContext ; 13 | TwFile mTwFile ; 14 | WebView mWebView ; 15 | public static final String LOG_TAG = "32XND-WebAppInterface"; 16 | 17 | WebAppInterface(Context c, TwFile twFile, WebView webView) { 18 | mContext = c; mTwFile = twFile ; mWebView = webView ; 19 | } 20 | 21 | @JavascriptInterface 22 | public void saveFile(String pathname, String text) { 23 | //Toast.makeText(mContext,pathname,Toast.LENGTH_SHORT).show(); 24 | Log.d(LOG_TAG, "saveFile - being called"); 25 | mTwFile.saveFile(text,mContext); 26 | /* try { 27 | //Log.e(LOG_TAG, dir + "/" + filename); 28 | FileOutputStream outputStream = new FileOutputStream(pathname); 29 | outputStream.write(text.getBytes()); 30 | outputStream.close(); 31 | } catch (Exception e) { 32 | Log.d(LOG_TAG, "WAI: error" + e.getMessage()); 33 | e.printStackTrace(); 34 | }*/ 35 | } 36 | 37 | @JavascriptInterface 38 | public void reload() { 39 | Log.d(LOG_TAG, "reload: being invoked"); 40 | try { 41 | // String temp = "file:///"+mTwFile.getUnschemedFilePath() ; 42 | //Log.d(LOG_TAG, "About to load file " + temp); 43 | Log.d(LOG_TAG, "About to reload file."); 44 | mWebView.post(new Runnable() { 45 | @Override 46 | public void run() { 47 | mWebView.reload(); 48 | } 49 | }) ; 50 | //mWebView.loadUrl(temp); 51 | //mWebView.reload(); 52 | } catch(Exception e) { Log.d(LOG_TAG,"Exception while trying to reload: " + e.getMessage()); 53 | e.printStackTrace(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_display_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_twresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 23 | 24 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_twresource_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 19 | 20 | 32 | 33 | 47 | 48 | 64 | 65 | 73 | 74 | 75 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tw_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tw_fragment_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tw_fragment_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 30 | 31 | 32 | 33 | 41 | 42 | 55 | 56 | 57 | 65 | 66 | 76 | 77 | 78 | 86 | 87 | 97 | 98 | 106 | 107 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tw_pager_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tw_resource_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 |

5 | 6 | 12 | 17 | 18 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #a1a9a8 5 | #D81B60 6 | #FFA1A9A8 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Quinoid 3 | Testing 190120-1808 4 | Local Explorer 5 | Settings 6 | System Explorer 7 | PREFERENCES-STRING 8 | CHOICES 9 | Use this title for display: 10 | Confirm 11 | Cancel 12 | System Picker 13 | Local Picker (depr) 14 | Exit 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 17 | 18 | 23 | 24 | 25 | 26 | 41 | 42 | 47 | 48 | 49 | --------------------------------------------------------------------------------