Skip to content

Commit

Permalink
Replace Kotlin Android sample with Java Android sample (#529)
Browse files Browse the repository at this point in the history
* replace Kotlin android sample with Java sample
  • Loading branch information
sbSteveK authored Jan 19, 2024
1 parent ebf80fc commit 99c68c6
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 238 deletions.
13 changes: 9 additions & 4 deletions samples/Android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,17 @@ files linked below.
### Files required by all samples:
* `endpoint.txt` - IoT ATS Endpoint

### Required to run BasicPubSub, Mqtt5PubSub, Jobs, and Shadow samples
### Required to run BasicPubSub and Mqtt5PubSub samples
* `certificate.pem` - IoT Thing Certificate
* `privatekey.pem` - IoT Thing Private Key
###### Optional Files for BasicPubSub and Mqtt5PubSub samples
* `topic.txt` - specifies --topic CLI argument
* `message.txt` - specifies --message CLI argument

### Required to run Jobs and Shadow sample
* `certificate.pem` - IoT Thing Certificate
* `privatekey.pem` - IoT Thing Private Key
* `thingName.txt` - IoT Thing Name used by sample

### Required to run Cognito Client sample:
* `cognitoIdentity.txt` - Cognito identity ID
Expand All @@ -40,10 +48,7 @@ files linked below.
### Optional files:
* `rootca.pem` - override the default system trust store
* `clientId.txt` - specifies --clientId CLI argument
* `topic.txt` - specifies --topic CLI argument
* `message.txt` - specifies --message CLI argument
* `port.txt` - specifies --port CLI argument
* `thingName.txt` - specifies --thingName CLI argument

# Build and install sample app

Expand Down
1 change: 0 additions & 1 deletion samples/Android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api 'software.amazon.awssdk.iotdevicesdk:aws-iot-device-sdk-android:1.19.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core:1.2.0'
implementation 'androidx.core:core-ktx:1.2.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
package software.amazon.awssdk.iotsamples;

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.io.IOException;
import java.io.PrintStream;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;


public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {

// Samples to load for the Android App
private static final Map<String, String> SAMPLES = new HashMap<String, String>() {{
put("Select a Sample",""); // empty default
put("Publish/Subscribe MQTT5 Sample", "mqtt5.pubsub.PubSub");
put("Publish/Subscribe MQTT3 Sample", "pubsub.PubSub");
put("Jobs Client Sample", "jobs.JobsSample");
put("Shadow Client Sample", "shadow.ShadowSample");
put("Cognito Client Sample", "cognitoconnect.CognitoConnect");
}};

private static final Logger logger = Logger.getLogger(MainActivity.class.getName());
private final StreamTee stdout;
private final StreamTee stderr;
private TextView console;
private Spinner sampleSelect;

private interface LogCallback {
void log(String message);
}

private class StreamTee extends PrintStream {
private final OutputStream source;
private final LogCallback logCallback;

public StreamTee(OutputStream source, LogCallback logCallback) {
super(source, true);
this.source = source;
this.logCallback = logCallback;
if (source == System.out) {
System.setOut(this);
} else if (source == System.err) {
System.setErr(this);
}
}

@Override
public void write(byte[] buf, int off, int len) {
try {
source.write(buf, off, len);
} catch (IOException e) {
logger.log(Level.SEVERE, "An exception occurred in StreamTee", e);
}
logCallback.log(new String(buf, off, len));
}

@Override
public void write(byte[] b) {
try {
source.write(b);
} catch (IOException e) {
logger.log(Level.SEVERE, "An exception occurred in StreamTee", e);
}
logCallback.log(new String(b));
}
}

public MainActivity() {
super();
stdout = new StreamTee(System.out, this::writeToConsole);
stderr = new StreamTee(System.err, this::writeToConsole);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

console = findViewById(R.id.console);
console.setEnabled(false);

sampleSelect = findViewById(R.id.sampleSelect);
ArrayAdapter<String> samplesAdapter = new ArrayAdapter<>(
this, R.layout.support_simple_spinner_dropdown_item, SAMPLES.keySet().toArray(new String[0])
);
sampleSelect.setAdapter(samplesAdapter);
sampleSelect.setOnItemSelectedListener(this);

loadAssets();
}

private void clearConsole() {
runOnUiThread(() -> console.setText(""));
}

private void writeToConsole(String message) {
runOnUiThread(() -> console.append(message));
}

private void onSampleComplete() {
runOnUiThread(() -> {
writeToConsole("\nSample Complete\n");
sampleSelect.setSelection(0);
sampleSelect.setEnabled(true);
});
}

private String assetContents(String assetName) throws IOException {
try (InputStream res = getResources().getAssets().open(assetName)) {
byte[] bytes = new byte[res.available()];
res.read(bytes);
return new String(bytes).trim();
} catch (IOException e) {
throw new IOException("Error reading asset file: " + assetName, e);
}
}

Map<String, String> resourceMap = new HashMap<>();

// Load files from assets folder for use into resourceMap
private void loadAssets(){

writeToConsole("Loading Asset Files:\n");
// Sample asset files in the assets folder
List<String> resourceNames = new ArrayList<>();
resourceNames.add("endpoint.txt");
resourceNames.add("certificate.pem");
resourceNames.add("privatekey.pem");
resourceNames.add("cognitoIdentity.txt");
resourceNames.add("signingRegion.txt");
resourceNames.add("port.txt");
resourceNames.add("clientId.txt");
resourceNames.add("topic.txt");
resourceNames.add("message.txt");
resourceNames.add("thingName.txt");
resourceNames.add("rootca.pem");

// Copy to cache and store file locations for file assets and contents for .txt assets
for (String resourceName : resourceNames) {
try {
try (InputStream res = getResources().getAssets().open(resourceName)) {
// .txt files will store contents of the file
if(resourceName.endsWith(".txt")){
byte[] bytes = new byte[res.available()];
res.read(bytes);
String contents = new String(bytes).trim();
resourceMap.put(resourceName, contents);
writeToConsole("'" + resourceName + "' file found and contents copied\n");
} else {
// non .txt file types will copy to cache and store accessible file location
String cachedName = getExternalCacheDir() + "/" + resourceName;
try (OutputStream cachedRes = new FileOutputStream(cachedName)) {
byte[] buffer = new byte[1024];
int length;
while ((length = res.read(buffer)) != -1) {
cachedRes.write(buffer, 0, length);
}
}
resourceMap.put(resourceName, cachedName);
writeToConsole("'" + resourceName + "' file found and cached\n");
}
}
} catch (IOException e) {
writeToConsole("'" + resourceName + "' file not found\n");
}
}
}

// Set a required argument from loaded assets
private boolean argSetRequired(String argName, String fileName, List<String> args){
if(resourceMap.containsKey(fileName)){
args.addAll(List.of(argName, resourceMap.get(fileName)));
return true;
}
writeToConsole("Required argument '" + argName + "' needs to be set. '" + fileName + "' File missing from assets folder\n");
return false;
}

// Check for optional argument and set if it's available
private void argSetOptional(String argName, String fileName, List<String>args){
if(resourceMap.containsKey(fileName)){
args.addAll(List.of(argName, resourceMap.get(fileName)));
}
}

// Retreive sample specific arguments needed from loaded asset files
private String[] sampleArgs(String sampleClassName){
List<String> args = new ArrayList<>();

// Every sample requires the endpoint argument
if (!argSetRequired("--endpoint", "endpoint.txt", args)){
return null;
}

// Shared optional arguments
argSetOptional("--verbosity", "verbosity.txt", args);
argSetOptional("--ca_file", "rootca.pem", args);
argSetOptional("--port", "port.txt", args);
argSetOptional("--client_id", "clientId.txt", args);

// Missing required arguments will return null
switch(sampleClassName){
case "mqtt5.pubsub.PubSub":
case "pubsub.PubSub":
if (!argSetRequired("--cert", "certificate.pem", args) ||
!argSetRequired("--key", "privatekey.pem", args)) {
return null;
}
argSetOptional("--topic", "topic.txt", args);
argSetOptional("--message", "message.txt", args);
break;

case "jobs.JobsSample":
case "shadow.ShadowSample":
if (!argSetRequired("--cert", "certificate.pem", args) ||
!argSetRequired("--key", "privatekey.pem", args) ||
!argSetRequired("--thing_name", "thingName.txt", args)) {
return null;
}
break;

case "cognitoconnect.CognitoConnect":
if (!argSetRequired("--cognito_identity", "cognitoIdentity.txt", args) ||
!argSetRequired("--signing_region", "signingRegion.txt", args)) {
return null;
}
break;
}

return args.toArray(new String[0]);
}

public class SampleRunnable implements Runnable {
private final String[] args;
private final Method sampleMain;

public SampleRunnable(String[] args, Method sampleMain){
this.args = args;
this.sampleMain = sampleMain;
}

@Override
public void run(){
try {
sampleMain.invoke(null, (Object) args);
} catch (Exception e){
writeToConsole("Exception occurred in run()" + e.toString() + "\n");
}
onSampleComplete();
}
}

private void runSample(String sampleName, String sampleClassName){
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> sampleClass = null;

try {
sampleClass = classLoader.loadClass(sampleClassName);
} catch (ClassNotFoundException e){
writeToConsole("Cound not find sample '" + sampleClassName + "'\n");
return;
}

if(sampleClass != null){
Method main = null;
try {
main = sampleClass.getMethod("main", String[].class);
if (main != null){
sampleSelect.setEnabled(false);
writeToConsole("Running '" + sampleName + "''\n\n");
String[] args = sampleArgs(sampleClassName);
if (args == null){
writeToConsole("Missing required arguments/files\n");
onSampleComplete();
return;
}
new Thread(new SampleRunnable(sampleArgs(sampleClassName), main), "sample_runner").start();
}
} catch (Exception e) {
writeToConsole("Exception encountered: " + e.toString());
onSampleComplete();
}
}
}

@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
String sampleName = parent.getItemAtPosition(pos).toString();
if (sampleName != "Select a Sample") {
clearConsole();
String sampleClassName = SAMPLES.get(sampleName);
if (sampleClassName != null) {
runSample(sampleName, sampleClassName);
}
}
}

@Override
public void onNothingSelected(AdapterView<?> parent) {
clearConsole();
writeToConsole("Please select a sample above");
}
}
Loading

0 comments on commit 99c68c6

Please sign in to comment.