Skip to content

Commit 19d76d4

Browse files
committed
Projects - Added Import & Export via menu on SCRIPTS button
1 parent 6aae70d commit 19d76d4

File tree

2 files changed

+279
-14
lines changed

2 files changed

+279
-14
lines changed

app/src/main/java/com/samsung/microbit/ui/activity/ProjectActivity.java

Lines changed: 268 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.samsung.microbit.ui.activity;
22

33
import android.Manifest;
4+
import android.annotation.SuppressLint;
45
import android.app.Activity;
56
import android.app.AlertDialog;
67
import android.bluetooth.BluetoothAdapter;
@@ -21,8 +22,10 @@
2122
import android.provider.DocumentsContract;
2223
import android.util.Log;
2324
import android.view.Menu;
25+
import android.view.MenuItem;
2426
import android.view.View;
2527
import android.view.Window;
28+
import android.webkit.ValueCallback;
2629
import android.webkit.WebChromeClient;
2730
import android.widget.LinearLayout;
2831
import android.widget.ListView;
@@ -31,6 +34,7 @@
3134

3235
import androidx.annotation.NonNull;
3336
import androidx.annotation.RequiresApi;
37+
import androidx.appcompat.widget.PopupMenu;
3438
import androidx.core.app.ActivityCompat;
3539
import androidx.core.content.ContextCompat;
3640
import androidx.core.content.PermissionChecker;
@@ -62,6 +66,7 @@
6266
import com.samsung.microbit.utils.Utils;
6367
import com.samsung.microbit.utils.irmHexUtils;
6468

69+
import java.io.BufferedReader;
6570
import java.io.ByteArrayOutputStream;
6671
import java.io.File;
6772
import java.io.FileInputStream;
@@ -70,6 +75,7 @@
7075
import java.io.FilenameFilter;
7176
import java.io.IOException;
7277
import java.io.InputStream;
78+
import java.io.InputStreamReader;
7379
import java.io.OutputStream;
7480
import java.net.URLDecoder;
7581
import java.nio.ByteBuffer;
@@ -99,6 +105,8 @@
99105
import static com.samsung.microbit.ui.activity.PopUpActivity.INTENT_GIFF_ANIMATION_CODE;
100106
import static com.samsung.microbit.utils.FileUtils.getFileSize;
101107

108+
import org.microbit.android.partialflashing.HexUtils;
109+
102110
// import com.samsung.microbit.core.GoogleAnalyticsManager;
103111

104112
/**
@@ -768,7 +776,7 @@ private void setConnectedDeviceText() {
768776
|| mActivityState == FlashActivityState.FLASH_STATE_WAIT_DEVICE_REBOOT
769777
|| mActivityState == FlashActivityState.FLASH_STATE_INIT_DEVICE
770778
|| mActivityState == FlashActivityState.FLASH_STATE_PROGRESS
771-
) {
779+
) {
772780
// connectedIndicatorIcon.setImageResource(R.drawable.device_status_connected);
773781
connectedIndicatorText.setText(getString(R.string.connected_to));
774782

@@ -903,8 +911,19 @@ private void setupListAdapter() {
903911

904912
@Override
905913
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
914+
switch ( requestCode) {
915+
case REQUEST_CODE_IMPORT:
916+
onActivityResultScriptsImport( requestCode, resultCode, data);
917+
super.onActivityResult(requestCode, resultCode, data);
918+
return;
919+
case REQUEST_CODE_EXPORT:
920+
onActivityResultScriptsExport( requestCode, resultCode, data);
921+
super.onActivityResult(requestCode, resultCode, data);
922+
return;
923+
}
924+
906925
boolean flash = mActivityState == FlashActivityState.STATE_ENABLE_BT_INTERNAL_FLASH_REQUEST ||
907-
mActivityState == FlashActivityState.STATE_ENABLE_BT_EXTERNAL_FLASH_REQUEST;
926+
mActivityState == FlashActivityState.STATE_ENABLE_BT_EXTERNAL_FLASH_REQUEST;
908927
boolean connect = mActivityState == FlashActivityState.STATE_ENABLE_BT_FOR_CONNECT;
909928

910929
if (requestCode == RequestCodes.REQUEST_ENABLE_BT) {
@@ -982,6 +1001,7 @@ private void proceedAfterBlePermissionGrantedAndBleEnabled() {
9821001
/**
9831002
* Starts activity to enable bluetooth.
9841003
*/
1004+
@SuppressLint("MissingPermission")
9851005
private void enableBluetooth() {
9861006
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
9871007
startActivityForResult(enableBtIntent, RequestCodes.REQUEST_ENABLE_BT);
@@ -991,7 +1011,7 @@ private boolean havePermission(String permission) {
9911011
return ContextCompat.checkSelfPermission( this, permission) == PermissionChecker.PERMISSION_GRANTED;
9921012
}
9931013

994-
private boolean havePermissionsFlashing() {
1014+
private boolean havePermissionsFlashing() {
9951015
boolean yes = true;
9961016
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
9971017
if ( !havePermission( Manifest.permission.BLUETOOTH_CONNECT))
@@ -1103,15 +1123,8 @@ public void sendProject(final Project project) {
11031123
@Override
11041124
public void onClick(final View v) {
11051125
switch(v.getId()) {
1106-
case R.id.createProject: {
1107-
Intent launchMakeCodeIntent = new Intent(this, MakeCodeWebView.class);
1108-
startActivity(launchMakeCodeIntent);
1109-
/*
1110-
Intent intent = new Intent(Intent.ACTION_VIEW);
1111-
intent.setData(Uri.parse(getString(R.string.my_scripts_url)));
1112-
startActivity(intent);
1113-
*/
1114-
}
1126+
case R.id.createProject:
1127+
scriptsPopup();
11151128
break;
11161129

11171130
case R.id.backBtn:
@@ -1781,7 +1794,6 @@ private String[] universalHexToDFU(String inputPath, int hardwareType) {
17811794
// return new String[]{"-1", "-1"};
17821795
// }
17831796

1784-
17851797
private void pfRegister() {
17861798
if (pfRegistered) {
17871799
return;
@@ -1922,7 +1934,7 @@ public void onClick(View v) {
19221934
},//override click listener for ok button
19231935
null);//pass null to use default listener
19241936
} else if(intent.getAction().equals(PartialFlashingService.BROADCAST_PF_ATTEMPT_DFU)) {
1925-
Log.v(TAG, "Use Nordic DFU");
1937+
Log.v(TAG, "Use Nordic DFU");
19261938
startDFUFlash();
19271939
} else if(intent.getAction().equals(PartialFlashingService.BROADCAST_PF_FAILED)) {
19281940

@@ -2294,4 +2306,246 @@ public boolean onCreateOptionsMenu(Menu menu) {
22942306
return true;
22952307
}
22962308

2309+
2310+
2311+
private void scriptsPopup() {
2312+
PopupMenu popupMenu = new PopupMenu( this, findViewById(R.id.createProject));
2313+
int itemID = Menu.FIRST;
2314+
popupMenu.getMenu().add( 0, itemID, 0, "Create Code");
2315+
itemID++;
2316+
popupMenu.getMenu().add( 0, itemID, 1, "Import");
2317+
itemID++;
2318+
popupMenu.getMenu().add( 0, itemID, 2, "Export");
2319+
itemID++;
2320+
2321+
popupMenu.setOnMenuItemClickListener( new PopupMenu.OnMenuItemClickListener() {
2322+
@Override
2323+
public boolean onMenuItemClick(MenuItem item) {
2324+
switch ( item.getItemId() - Menu.FIRST) {
2325+
case 0: scriptsCreateCode(); break;
2326+
case 1: scriptsImport(); break;
2327+
case 2: scriptsExport(); break;
2328+
}
2329+
return false;
2330+
}
2331+
});
2332+
popupMenu.show();
2333+
}
2334+
2335+
private void scriptsCreateCode() {
2336+
Intent launchMakeCodeIntent = new Intent(this, MakeCodeWebView.class);
2337+
startActivity(launchMakeCodeIntent);
2338+
}
2339+
2340+
private static final int REQUEST_CODE_EXPORT = 1;
2341+
private static final int REQUEST_CODE_IMPORT = 2;
2342+
2343+
2344+
private void scriptsImport() {
2345+
String messageTitle = "Import";
2346+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
2347+
intent.addCategory(Intent.CATEGORY_OPENABLE);
2348+
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2349+
intent.setType("application/octet-stream");
2350+
startActivityForResult( Intent.createChooser(intent, messageTitle), REQUEST_CODE_IMPORT);
2351+
}
2352+
2353+
protected void onActivityResultScriptsImport(int requestCode, int resultCode, Intent data) {
2354+
if ( resultCode != RESULT_OK) {
2355+
return;
2356+
}
2357+
Toast.makeText(this, "Importing project", Toast.LENGTH_LONG).show();
2358+
new Thread( new Runnable() {
2359+
@Override
2360+
public void run() {
2361+
int error = scriptsImportOpen( data.getData());
2362+
runOnUiThread(new Runnable() {
2363+
@Override
2364+
public void run() {
2365+
switch (error) {
2366+
case 0:
2367+
if ( minimumPermissionsGranted) {
2368+
updateProjectsListSortOrder(true);
2369+
}
2370+
break;
2371+
case 1:
2372+
Toast.makeText( ProjectActivity.this,
2373+
"Project import failed", Toast.LENGTH_LONG).show();
2374+
break;
2375+
case 2:
2376+
Toast.makeText( ProjectActivity.this,
2377+
"A project with the same name already exists",
2378+
Toast.LENGTH_LONG).show();
2379+
break;
2380+
}
2381+
}
2382+
});
2383+
}
2384+
}).start();
2385+
}
2386+
2387+
private int scriptsImportOpen( Uri uri) {
2388+
String fileName = "microbit-import.hex";
2389+
2390+
String scheme = uri.getScheme();
2391+
String mime = getContentResolver().getType(uri);
2392+
if ( scheme.equals("file")) {
2393+
String encodedPath = uri.getEncodedPath();
2394+
String path = URLDecoder.decode(encodedPath);
2395+
fileName = fileNameForFlashing( path);
2396+
} else if( scheme.equals("content")) {
2397+
Cursor cursor = null;
2398+
cursor = getContentResolver().query(uri, null, null, null, null);
2399+
if (cursor != null && cursor.moveToFirst()) {
2400+
int index = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
2401+
if (index >= 0) {
2402+
fileName = cursor.getString(index);
2403+
}
2404+
}
2405+
}
2406+
2407+
String projectPath = ProjectsHelper.projectPath(this, fileName);
2408+
if ( FileUtils.fileExists( projectPath)) {
2409+
return 2;
2410+
}
2411+
2412+
boolean ok = true;
2413+
FileInputStream fis = null;
2414+
BufferedReader reader = null;
2415+
try {
2416+
IOUtils.copy(getContentResolver().openInputStream(uri), new FileOutputStream(projectPath));
2417+
2418+
// Check file is hex
2419+
int lineCount = 0;
2420+
fis = new FileInputStream( projectPath);
2421+
reader = new BufferedReader( new InputStreamReader( fis));
2422+
while ( true) {
2423+
String line = reader.readLine();
2424+
if ( line == null) {
2425+
break;
2426+
}
2427+
lineCount++;
2428+
if ( !line.isEmpty() && !line.startsWith(":")) {
2429+
ok = false;
2430+
break;
2431+
}
2432+
if ( lineCount == 0) {
2433+
ok = false;
2434+
}
2435+
}
2436+
} catch (Exception e) {
2437+
logi( e.toString());
2438+
ok = false;
2439+
}
2440+
2441+
if ( reader != null) {
2442+
try {
2443+
reader.close();
2444+
} catch (IOException e) {
2445+
}
2446+
}
2447+
2448+
if ( fis != null) {
2449+
try {
2450+
fis.close();
2451+
} catch (IOException e) {
2452+
}
2453+
}
2454+
2455+
if ( !ok) {
2456+
FileUtils.deleteFile( projectPath);
2457+
}
2458+
return ok ? 0 : 1;
2459+
}
2460+
2461+
private void scriptsExport() {
2462+
String messageTitle = "Export";
2463+
String name = "microbit-projects";
2464+
String mimetype = "application/zip";
2465+
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
2466+
intent.addCategory(Intent.CATEGORY_OPENABLE);
2467+
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
2468+
intent.setType( mimetype);
2469+
intent.putExtra(Intent.EXTRA_TITLE, name);
2470+
startActivityForResult( Intent.createChooser(intent, messageTitle), REQUEST_CODE_EXPORT);
2471+
}
2472+
2473+
protected void onActivityResultScriptsExport(int requestCode, int resultCode, Intent data) {
2474+
if ( resultCode != RESULT_OK) {
2475+
return;
2476+
}
2477+
Toast.makeText(this, "Saving Projects ZIP file", Toast.LENGTH_LONG).show();
2478+
new Thread( new Runnable() {
2479+
@Override
2480+
public void run() {
2481+
int error = scriptsExportSave( data.getData());
2482+
runOnUiThread(new Runnable() {
2483+
@Override
2484+
public void run() {
2485+
switch ( error) {
2486+
case 0:
2487+
Toast.makeText( ProjectActivity.this,
2488+
"Saved Projects ZIP file", Toast.LENGTH_LONG).show();
2489+
break;
2490+
case 1:
2491+
Toast.makeText( ProjectActivity.this,
2492+
"Projects export failed", Toast.LENGTH_LONG).show();
2493+
break;
2494+
case 2:
2495+
Toast.makeText( ProjectActivity.this,
2496+
"A file with the same name already exists",
2497+
Toast.LENGTH_LONG).show();
2498+
break;
2499+
}
2500+
}
2501+
});
2502+
}
2503+
}).start();
2504+
}
2505+
2506+
private int scriptsExportSave( Uri uri) {
2507+
boolean ok = true;
2508+
2509+
byte[] buffer = new byte[1024];
2510+
File[] projects = ProjectsHelper.projectFilesListHEX( this);
2511+
2512+
OutputStream os = null;
2513+
ZipOutputStream zipOutputStream = null;
2514+
FileInputStream fileInputStream = null;
2515+
try {
2516+
os = getContentResolver().openOutputStream( uri);
2517+
zipOutputStream = new ZipOutputStream(os);
2518+
for ( int i = 0; i < projects.length; i++) {
2519+
fileInputStream = new FileInputStream( projects[i]);
2520+
zipOutputStream.putNextEntry(new ZipEntry( projects[i].getName()));
2521+
2522+
int length;
2523+
while ((length = fileInputStream.read(buffer)) > 0) {
2524+
zipOutputStream.write(buffer, 0, length);
2525+
}
2526+
2527+
zipOutputStream.closeEntry();
2528+
fileInputStream.close();
2529+
fileInputStream = null;
2530+
}
2531+
zipOutputStream.close();
2532+
os.close();
2533+
} catch (Exception e) {
2534+
ok = false;
2535+
try {
2536+
if ( fileInputStream != null) {
2537+
fileInputStream.close();
2538+
}
2539+
if ( zipOutputStream != null) {
2540+
zipOutputStream.close();
2541+
}
2542+
if ( os != null) {
2543+
os.close();
2544+
}
2545+
} catch (Exception e2) {
2546+
e.printStackTrace();
2547+
}
2548+
}
2549+
return ok ? 0 : 1;
2550+
}
22972551
}

app/src/main/java/com/samsung/microbit/utils/FileUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ public static RenameResult renameFile(String filePath, String newName) {
6464
}
6565
}
6666

67+
/**
68+
* Check if a path is a file
69+
*
70+
* @param filePath Full file path.
71+
* @return True if the file exists.
72+
*/
73+
public static boolean fileExists( String filePath) {
74+
File file = new File( filePath);
75+
return file.exists() && file.isFile();
76+
}
77+
6778
/**
6879
* Tries to delete a file by given path.
6980
*

0 commit comments

Comments
 (0)