22
33import android .content .ContentValues ;
44import android .content .Intent ;
5+ import android .content .SharedPreferences ;
56import android .net .Uri ;
67import android .os .Bundle ;
78import android .os .Environment ;
9+ import android .os .Handler ;
10+ import android .os .Looper ;
811import android .provider .MediaStore ;
912import android .util .Base64 ;
1013import android .util .Log ;
1316
1417import androidx .activity .OnBackPressedCallback ;
1518import androidx .appcompat .app .AppCompatActivity ;
16- import androidx .core .content .FileProvider ;
1719import androidx .core .content .IntentCompat ;
1820
1921import java .io .*;
@@ -22,80 +24,69 @@ public class MainActivity extends AppCompatActivity {
2224
2325 private static final String TAG = "NoteOptimizer" ;
2426 private static final String APP_URL = "https://nrontsis.github.io/boox-note-optimizer" ;
25- private static final String PROVIDER = "io.github.nrontsis.noteoptimizer.fileprovider" ;
2627 private static final String BOOX_NOTES = "com.onyx.android.note" ;
28+ private static final String PREFS = "exports" ;
29+ private static final String PREF_LAST_URI = "last_export_uri" ;
2730
2831 private WebView webView ;
32+ private final Handler handler = new Handler (Looper .getMainLooper ());
2933
3034 private static final String EXPORT_FOLDER = Environment .DIRECTORY_DOWNLOADS + "/Note Optimizer" ;
3135
32- /** Write file to Downloads/Note Optimizer via MediaStore. Cleans folder first. */
36+ /** Write file to Downloads/Note Optimizer via MediaStore. Deletes previous export first. */
3337 private Uri writeToDownloads (byte [] data , String fileName ) throws IOException {
34- // Clean all previous exports in our subfolder
35- try {
36- getContentResolver (). delete (
37- MediaStore . Downloads . EXTERNAL_CONTENT_URI ,
38- MediaStore . Downloads . RELATIVE_PATH + " IN (?,?)" ,
39- new String []{ EXPORT_FOLDER + "/" , EXPORT_FOLDER });
40- } catch ( Exception ignored ) {}
38+ // Delete previous export by its exact URI
39+ SharedPreferences prefs = getSharedPreferences ( PREFS , MODE_PRIVATE );
40+ String lastUri = prefs . getString ( PREF_LAST_URI , null );
41+ if ( lastUri != null ) {
42+ try { getContentResolver (). delete ( Uri . parse ( lastUri ), null , null ); }
43+ catch ( Exception ignored ) {}
44+ }
4145
4246 ContentValues cv = new ContentValues ();
4347 cv .put (MediaStore .Downloads .DISPLAY_NAME , fileName );
4448 cv .put (MediaStore .Downloads .MIME_TYPE , "application/octet-stream" );
4549 cv .put (MediaStore .Downloads .RELATIVE_PATH , EXPORT_FOLDER );
46- cv .put (MediaStore .Downloads .IS_PENDING , 1 );
4750 Uri uri = getContentResolver ().insert (MediaStore .Downloads .EXTERNAL_CONTENT_URI , cv );
4851 if (uri == null ) throw new IOException ("Failed to create Downloads entry" );
4952 try (OutputStream os = getContentResolver ().openOutputStream (uri )) {
5053 if (os == null ) throw new IOException ("Failed to open output stream" );
5154 os .write (data );
5255 }
53- // Mark as complete so other apps can read it
54- ContentValues done = new ContentValues ();
55- done .put (MediaStore .Downloads .IS_PENDING , 0 );
56- getContentResolver ().update (uri , done , null , null );
56+
57+ prefs .edit ().putString (PREF_LAST_URI , uri .toString ()).apply ();
5758 return uri ;
5859 }
5960
60- /** Try to open a file in Boox Notes via MediaStore URI, then chooser . */
61+ /** Open a file in Boox Notes, with a single retry to work around MediaStore lag . */
6162 private void openInBooxNotes (Uri uri ) {
62- String [] actions = {Intent .ACTION_VIEW , Intent .ACTION_SEND };
63- String [] mimes = {"application/zip" , "application/x-zip-compressed" ,
64- "application/octet-stream" , "*/*" };
65-
66- for (String action : actions ) {
67- for (String mime : mimes ) {
68- Intent intent = new Intent (action );
69- if (Intent .ACTION_VIEW .equals (action )) {
70- intent .setDataAndType (uri , mime );
71- } else {
72- intent .setType (mime );
73- intent .putExtra (Intent .EXTRA_STREAM , uri );
74- }
75- intent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
76- intent .setPackage (BOOX_NOTES );
77- try {
78- startActivity (intent );
79- return ;
80- } catch (Exception ignored ) {}
81- }
82- }
63+ Intent intent = new Intent (Intent .ACTION_VIEW );
64+ intent .setDataAndType (uri , "*/*" );
65+ intent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
66+ intent .setPackage (BOOX_NOTES );
8367
84- // Fallback: chooser
85- Intent fallback = new Intent (Intent .ACTION_VIEW );
86- fallback .setDataAndType (uri , "application/octet-stream" );
87- fallback .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
88- startActivity (Intent .createChooser (fallback , "Open with" ));
68+ try {
69+ startActivity (intent );
70+ handler .postDelayed (() -> {
71+ try { startActivity (intent ); }
72+ catch (Exception ignored ) {}
73+ }, 1500 );
74+ } catch (Exception e ) {
75+ Intent fallback = new Intent (Intent .ACTION_VIEW );
76+ fallback .setDataAndType (uri , "*/*" );
77+ fallback .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
78+ startActivity (Intent .createChooser (fallback , "Open with" ));
79+ }
8980 }
9081
91- /* ── JS bridge: receives base64 file data from the web page ── */
82+ /* ── JS bridge ── */
9283 private class AndroidBridge {
9384 @ JavascriptInterface
9485 public void receiveFile (String base64 , String fileName ) {
9586 try {
9687 byte [] data = Base64 .decode (base64 , Base64 .DEFAULT );
9788 Uri uri = writeToDownloads (data , fileName );
98- openInBooxNotes (uri );
89+ runOnUiThread (() -> openInBooxNotes (uri ) );
9990 } catch (Exception e ) {
10091 Log .e (TAG , "receiveFile failed" , e );
10192 runOnUiThread (() ->
@@ -125,7 +116,6 @@ public void onPageFinished(WebView view, String url) {
125116 }
126117 });
127118
128- /* ── Intercept blob: downloads → fetch via JS → pass to native ── */
129119 webView .setDownloadListener ((url , userAgent , contentDisposition , mimetype , contentLength ) -> {
130120 if (url .startsWith ("blob:" )) {
131121 String fetchJs =
@@ -149,7 +139,7 @@ public void onPageFinished(WebView view, String url) {
149139 webView .loadUrl (APP_URL );
150140 }
151141
152- /* ── Inbound share: .note file → inject into web app cache ── */
142+ /* ── Inbound share ── */
153143 @ Override
154144 protected void onNewIntent (Intent intent ) {
155145 super .onNewIntent (intent );
@@ -162,7 +152,6 @@ private void handleInboundShare(Intent intent) {
162152 Uri uri = IntentCompat .getParcelableExtra (intent , Intent .EXTRA_STREAM , Uri .class );
163153 if (uri == null ) return ;
164154
165- // Clear the intent so we don't re-process it on page reload
166155 setIntent (new Intent (Intent .ACTION_MAIN ));
167156
168157 try {
@@ -177,7 +166,6 @@ private void handleInboundShare(Intent intent) {
177166 String base64 = Base64 .encodeToString (baos .toByteArray (), Base64 .NO_WRAP );
178167 String name = getFileName (uri );
179168
180- // Stash file on window; web app picks it up once WASM is ready
181169 String js =
182170 "(function() {" +
183171 " const b64 = '" + base64 + "';" +
0 commit comments