Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Kotlin Android sample with Java Android sample #529

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading