├── .gitignore ├── src ├── test │ ├── resources │ │ ├── test.wav │ │ └── invalid.wav │ └── java │ │ └── ai │ │ └── sapcai │ │ └── sdk_android │ │ └── SdkTests.java ├── main │ ├── res │ │ └── values │ │ │ └── strings.xml │ ├── AndroidManifest.xml │ └── java │ │ └── ai │ │ └── sapcai │ │ └── sdk_android │ │ ├── Intent.java │ │ ├── Action.java │ │ ├── SapcaiException.java │ │ ├── Entity.java │ │ ├── MemoryEntity.java │ │ ├── Memory.java │ │ ├── SapcaiRecorder.java │ │ ├── MultipartUtility.java │ │ ├── Request.java │ │ ├── Response.java │ │ ├── Client.java │ │ └── Conversation.java └── androidTest │ └── java │ └── ai │ └── sapcai │ └── sdk_android │ └── ApplicationTest.java ├── LICENSE ├── README.md └── sdk-android.iml /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | build.gradle 3 | sdk-android.iml 4 | -------------------------------------------------------------------------------- /src/test/resources/test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/SDK-Android/HEAD/src/test/resources/test.wav -------------------------------------------------------------------------------- /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | sdk-android 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/invalid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/SDK-Android/HEAD/src/test/resources/invalid.wav -------------------------------------------------------------------------------- /src/androidTest/java/ai/sapcai/sdk_android/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Intent.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class Intent { 6 | private String name; 7 | private double confidence; 8 | 9 | private Intent(String name, double confidence) { 10 | this.name = name; 11 | this.confidence = confidence; 12 | } 13 | 14 | Intent(JSONObject obj) { 15 | this(obj.optString("slug"), obj.optDouble("confidence")); 16 | } 17 | 18 | public double getConfidence() { 19 | return confidence; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Action.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class Action { 6 | 7 | private String slug; 8 | private Boolean done; 9 | private String reply; 10 | 11 | private Action(String slug, Boolean done, String reply) { 12 | this.slug = slug; 13 | this.done = done; 14 | this.reply = reply; 15 | } 16 | 17 | Action (JSONObject obj) { 18 | this(obj.optString("slug"), obj.optBoolean("done"), obj.optString("reply")); 19 | } 20 | 21 | public String getSlug() { 22 | return slug; 23 | } 24 | 25 | public void setSlug(String slug) { 26 | this.slug = slug; 27 | } 28 | 29 | public Boolean getDone() { 30 | return done; 31 | } 32 | 33 | public void setDone(Boolean done) { 34 | this.done = done; 35 | } 36 | 37 | public String getReply() { 38 | return reply; 39 | } 40 | 41 | public void setReply(String reply) { 42 | this.reply = reply; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SAP Conversational AI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/SapcaiException.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class SapcaiException extends RuntimeException { 7 | private int statusCode; 8 | public SapcaiException(final String message) { 9 | super(message); 10 | this.statusCode = -1; 11 | } 12 | 13 | public SapcaiException(int statusCode) { 14 | this(getSapcaiErrorMessage(statusCode)); 15 | this.statusCode = statusCode; 16 | } 17 | 18 | public SapcaiException(final String message, final Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | private static String getSapcaiErrorMessage(int statusCode) { 23 | Map errorMessages = new HashMap<>(); 24 | errorMessages.put(400, "400: Bad request"); 25 | errorMessages.put(415, "415: Unsupported media type"); 26 | errorMessages.put(503, "503: Service unavailable"); 27 | errorMessages.put(401, "401: Unauthorized"); 28 | return errorMessages.get(statusCode); 29 | } 30 | 31 | public int getStatusCode() { 32 | return this.statusCode; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Entity.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.json.JSONObject; 4 | 5 | /** 6 | * The Entity class represents an entity found by SAP Conversational AI in the user input. 7 | * 8 | * @author Francois Triquet 9 | * @version 2.0.0 10 | * @since 2016-05-17 11 | * 12 | */ 13 | public class Entity { 14 | 15 | private String name; 16 | private double confidence; 17 | private JSONObject data; 18 | 19 | Entity (String name, JSONObject o) { 20 | this.data = o; 21 | this.name = name; 22 | if(o != null){ 23 | this.confidence = o.optDouble("confidence"); 24 | } 25 | } 26 | 27 | /** 28 | * Returns the name of the entity 29 | * @return The name of the entity 30 | */ 31 | public String getName() { 32 | return this.name; 33 | } 34 | 35 | /** 36 | * Returns the confidence of the entity 37 | * @return The confidence of the entity 38 | */ 39 | public double getConfidence() { 40 | return this.confidence; 41 | } 42 | 43 | 44 | /** 45 | * Returns the fields described by the parameter if it exists or null otherwise 46 | * @param name The name of the field 47 | * @return The value of the field or null 48 | */ 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/MemoryEntity.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class MemoryEntity { 6 | 7 | private String name; 8 | private String raw; 9 | private String value; 10 | private double confidence; 11 | 12 | public MemoryEntity(String entityName, JSONObject obj) { 13 | this.name = entityName; 14 | if(obj != null){ 15 | this.raw = obj.optString("raw"); 16 | this.value = obj.optString("value"); 17 | this.confidence = obj.optDouble("confidence"); 18 | } 19 | } 20 | 21 | public MemoryEntity(String raw, String value, double confidence){ 22 | this.raw = raw; 23 | this.value = value; 24 | this.confidence = confidence; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public String getRaw() { 36 | return raw; 37 | } 38 | 39 | public void setRaw(String raw) { 40 | this.raw = raw; 41 | } 42 | 43 | public String getValue() { 44 | return value; 45 | } 46 | 47 | public void setValue(String value) { 48 | this.value = value; 49 | } 50 | 51 | public double getConfidence() { 52 | return confidence; 53 | } 54 | 55 | public void setConfidence(double confidence) { 56 | this.confidence = confidence; 57 | } 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Memory.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import java.util.HashMap; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | 7 | import org.json.JSONObject; 8 | 9 | public class Memory { 10 | 11 | private Map entities; 12 | 13 | 14 | Memory(JSONObject obj) { 15 | 16 | 17 | this.entities = new HashMap(); 18 | if (obj.length() != 0) { 19 | Iterator it = obj.keys(); 20 | 21 | while (it.hasNext()) { 22 | String entityName = it.next(); 23 | JSONObject entity = obj.optJSONObject(entityName); 24 | if(entity != null){ 25 | this.entities.put(entityName, new MemoryEntity(entityName, entity)); 26 | }else{ 27 | this.entities.put(entityName, null); 28 | } 29 | } 30 | } 31 | } 32 | 33 | public String convertMemory(){ 34 | String memory = "{"; 35 | for(Map.Entry memap : this.entities.entrySet()){ 36 | String entityName = memap.getKey(); 37 | MemoryEntity me = memap.getValue(); 38 | if(me != null){ 39 | memory = memory + "\"" + entityName + "\":{\"raw\":\"" + me.getRaw() + 40 | "\", \"value\":\"" + me.getValue() + "\", \"confidence\":\"" + 41 | me.getConfidence() + "\"},"; 42 | }else{ 43 | memory = memory + "\"" + entityName + "\": null,"; 44 | } 45 | } 46 | memory = memory.substring(0, memory.length()-1); 47 | memory = memory + "}"; 48 | return memory; 49 | } 50 | 51 | public void setMemory(String name, MemoryEntity newEntity){ 52 | this.entities.put(name, newEntity); 53 | } 54 | 55 | public void resetMemory(){ 56 | this.entities = new HashMap(); 57 | } 58 | 59 | public Map getEntities() { 60 | return entities; 61 | } 62 | 63 | 64 | public void setEntities(Map entities) { 65 | this.entities = entities; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/SapcaiRecorder.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | 4 | import android.media.AudioFormat; 5 | import android.media.AudioRecord; 6 | import android.media.MediaRecorder; 7 | import android.util.Log; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.RandomAccessFile; 12 | 13 | class SapcaiRecorder { 14 | 15 | static final int SAMPLE_RATE = 44100; 16 | static final int CHANNELS = 1; 17 | static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT; 18 | private AudioRecord mRecorder = null; 19 | private boolean mRecording = false; 20 | private int mBufferSize; 21 | private int mFramePeriod; 22 | private static String TAG = "SapcaiRecorder"; 23 | private String mFilepath; 24 | private int mSize; 25 | private byte[] mBuffer; 26 | private RandomAccessFile mFileWriter; 27 | 28 | SapcaiRecorder (String file) throws SapcaiException { 29 | mFilepath = file; 30 | mSize = 0; 31 | 32 | mFramePeriod = SAMPLE_RATE * 120 / 1000; 33 | mBufferSize = mFramePeriod * 2 * 16 * 1 / 8; //2 * samples * channels / 8 34 | 35 | if (mBufferSize < AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, FORMAT)) { // Check to make sure buffer size is not smaller than the smallest allowed one 36 | mBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, FORMAT); 37 | // Set frame period and timer interval accordingly 38 | mFramePeriod = mBufferSize / (2 * 16 * 1 / 8); 39 | Log.w(TAG, "Increasing buffer size to " + Integer.toString(mBufferSize)); 40 | } 41 | 42 | mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 43 | SAMPLE_RATE, 44 | AudioFormat.CHANNEL_IN_MONO, 45 | FORMAT, 46 | mBufferSize); 47 | 48 | if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { 49 | throw new SapcaiException("Record initialization failed"); 50 | } 51 | mBuffer = new byte[mBufferSize]; 52 | try { 53 | this.initOutputFile(); 54 | } catch (IOException e) { 55 | throw new SapcaiException("Unable to open output file for recording"); 56 | } 57 | 58 | AudioRecord.OnRecordPositionUpdateListener updater = new AudioRecord.OnRecordPositionUpdateListener() { 59 | @Override 60 | public void onMarkerReached(AudioRecord recorder) { 61 | 62 | } 63 | 64 | @Override 65 | public void onPeriodicNotification(AudioRecord recorder) { 66 | try { 67 | int read = recorder.read(mBuffer, 0, mBuffer.length); 68 | mSize += read; 69 | mFileWriter.write(mBuffer); 70 | } catch (IOException e) { 71 | Log.w(TAG, "An error occured while reading audio input, aborting"); 72 | done(); 73 | } 74 | } 75 | }; 76 | 77 | mRecorder.setRecordPositionUpdateListener(updater); 78 | mRecorder.setPositionNotificationPeriod(mFramePeriod); 79 | } 80 | 81 | public void initOutputFile() throws IOException { 82 | File f = new File(mFilepath); 83 | if (f.exists()) { 84 | f.delete(); 85 | f.createNewFile(); 86 | } else { 87 | f.createNewFile(); 88 | } 89 | //WAV audio header 90 | mFileWriter = new RandomAccessFile(mFilepath, "rw"); 91 | mFileWriter.setLength(0); 92 | mFileWriter.writeBytes("RIFF"); 93 | mFileWriter.writeInt(0); 94 | mFileWriter.writeBytes("WAVE"); 95 | mFileWriter.writeBytes("fmt "); 96 | mFileWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM 97 | mFileWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM 98 | mFileWriter.writeShort(Short.reverseBytes((short)1));// Number of channels, 1 for mono, 2 for stereo 99 | mFileWriter.writeInt(Integer.reverseBytes(SAMPLE_RATE)); // Sample rate 100 | mFileWriter.writeInt(Integer.reverseBytes(SAMPLE_RATE * 16 * 1 / 8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8 101 | mFileWriter.writeShort(Short.reverseBytes((short) (CHANNELS * 16 / 8))); // Block align, NumberOfChannels*BitsPerSample/8 102 | mFileWriter.writeShort(Short.reverseBytes((short)16)); // Bits per sample 103 | mFileWriter.writeBytes("data"); 104 | mFileWriter.writeInt(0); // Data chunk size not known yet, write 0 105 | mBuffer = new byte[mFramePeriod * 16 / 8 * 1]; 106 | } 107 | 108 | public void done() { 109 | if (mRecorder != null) { 110 | mRecorder.stop(); 111 | mRecorder.release(); 112 | mRecorder = null; 113 | } 114 | } 115 | 116 | public void startRecording() throws SapcaiException { 117 | mRecording = true; 118 | mRecorder.startRecording(); 119 | mRecorder.read(mBuffer, 0, mBuffer.length); 120 | 121 | } 122 | 123 | public void stopRecording() throws IOException { 124 | mRecording = false; 125 | done(); 126 | mFileWriter.seek(4); 127 | mFileWriter.writeInt(Integer.reverseBytes(36 + mSize)); 128 | mFileWriter.seek(40); 129 | mFileWriter.writeInt(Integer.reverseBytes(mSize)); 130 | mFileWriter.close(); 131 | } 132 | 133 | public boolean isRecording() { 134 | return mRecording; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/MultipartUtility.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import java.io.BufferedReader; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStream; 10 | import java.io.OutputStreamWriter; 11 | import java.io.PrintWriter; 12 | import java.net.HttpURLConnection; 13 | import java.net.URL; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * This utility class provides an abstraction layer for sending multipart HTTP 19 | * POST requests to a web server. 20 | * 21 | */ 22 | class MultipartUtility { 23 | private final String boundary; 24 | private static final String LINE_FEED = "\r\n"; 25 | private HttpURLConnection httpConn; 26 | private String charset; 27 | private OutputStream outputStream; 28 | private PrintWriter writer; 29 | 30 | /** 31 | * This constructor initializes a new HTTP POST request with content type 32 | * is set to multipart/form-data 33 | * @param requestURL 34 | * @param charset 35 | * @throws IOException 36 | */ 37 | public MultipartUtility(String requestURL, String charset, String token) 38 | throws IOException { 39 | this.charset = charset; 40 | 41 | // creates a unique boundary based on time stamp 42 | boundary = "===" + System.currentTimeMillis() + "==="; 43 | 44 | URL url = new URL(requestURL); 45 | httpConn = (HttpURLConnection) url.openConnection(); 46 | httpConn.setUseCaches(false); 47 | httpConn.setDoOutput(true); // indicates POST method 48 | httpConn.setDoInput(true); 49 | httpConn.setRequestProperty("Content-Type", 50 | "multipart/form-data; boundary=" + boundary); 51 | httpConn.setRequestProperty("Authorization", "Token " + token); 52 | outputStream = httpConn.getOutputStream(); 53 | writer = new PrintWriter(new OutputStreamWriter(outputStream, charset), 54 | true); 55 | } 56 | 57 | /** 58 | * Adds a form field to the request 59 | * @param name field name 60 | * @param value field value 61 | */ 62 | public void addFormField(String name, String value) { 63 | writer.append("--" + boundary).append(LINE_FEED); 64 | writer.append("Content-Disposition: form-data; name=\"" + name + "\"") 65 | .append(LINE_FEED); 66 | writer.append("Content-Type: text/plain; charset=" + charset).append( 67 | LINE_FEED); 68 | writer.append(LINE_FEED); 69 | writer.append(value); 70 | writer.flush(); 71 | } 72 | 73 | /** 74 | * Adds a upload file section to the request 75 | * @param fieldName name attribute in 76 | * @param uploadFile a File to be uploaded 77 | * @throws IOException 78 | */ 79 | public void addFilePart(String fieldName, File uploadFile) 80 | throws IOException { 81 | String fileName = uploadFile.getName(); 82 | writer.append("--" + boundary).append(LINE_FEED); 83 | writer.append( 84 | "Content-Disposition: form-data; name=\"" + fieldName 85 | + "\"; filename=\"" + fileName + "\"") 86 | .append(LINE_FEED); 87 | writer.append("Content-Type: audio/wav").append(LINE_FEED); 88 | writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); 89 | writer.append(LINE_FEED); 90 | writer.flush(); 91 | 92 | FileInputStream inputStream = new FileInputStream(uploadFile); 93 | byte[] buffer = new byte[4096]; 94 | int bytesRead = -1; 95 | while ((bytesRead = inputStream.read(buffer)) != -1) { 96 | outputStream.write(buffer, 0, bytesRead); 97 | } 98 | outputStream.flush(); 99 | inputStream.close(); 100 | 101 | writer.append(LINE_FEED); 102 | writer.flush(); 103 | } 104 | 105 | /** 106 | * Adds a header field to the request. 107 | * @param name - name of the header field 108 | * @param value - value of the header field 109 | */ 110 | public void addHeaderField(String name, String value) { 111 | writer.append(name + ": " + value).append(LINE_FEED); 112 | writer.flush(); 113 | } 114 | 115 | /** 116 | * Completes the request and receives response from the server. 117 | * @return a list of Strings as response in case the server returned 118 | * status OK, otherwise an exception is thrown. 119 | * @throws IOException 120 | */ 121 | public List finish() { 122 | List response = new ArrayList(); 123 | 124 | writer.append(LINE_FEED).flush(); 125 | writer.append("--" + boundary + "--").append(LINE_FEED); 126 | writer.close(); 127 | 128 | try { 129 | // checks server's status code first 130 | int status = httpConn.getResponseCode(); 131 | if (status == HttpURLConnection.HTTP_OK) { 132 | BufferedReader reader = new BufferedReader(new InputStreamReader( 133 | httpConn.getInputStream())); 134 | String line = null; 135 | while ((line = reader.readLine()) != null) { 136 | response.add(line); 137 | } 138 | reader.close(); 139 | httpConn.disconnect(); 140 | } else { 141 | throw new SapcaiException(status); 142 | } 143 | } catch (IOException e) { 144 | throw new SapcaiException("Request Failed", e); 145 | } 146 | return response; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Request.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import android.os.Environment; 4 | import java.net.URL; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStream; 10 | import javax.net.ssl.HttpsURLConnection; 11 | import java.net.MalformedURLException; 12 | import java.io.File; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class Request { 18 | 19 | private static final String sapcaiAPI = "https://api.cai.tools.sap/v2/request"; 20 | private static final String converseAPI = "https://api.cai.tools.sap/v2/converse"; 21 | 22 | public String token; 23 | public String language; 24 | 25 | public Request(String token){ 26 | this.token = token; 27 | } 28 | 29 | public Request(String token, String language){ 30 | this.token = token; 31 | this.language = language; 32 | } 33 | 34 | /** 35 | * Performs a text request to SAP Conversational AI with the token of the Client 36 | * @param myText The text to be processed 37 | * @return The Response corresponding to the input 38 | * @throws SapcaiException if SAP Conversational AI can't process the text 39 | * @see Response 40 | */ 41 | public Response doTextRequest(String myText) throws SapcaiException { 42 | URL obj; 43 | try { 44 | obj = new URL(sapcaiAPI); 45 | String sapcaiJson = this.doApiRequest(myText, this.token, this.language, obj); 46 | return new Response(sapcaiJson); 47 | } catch (MalformedURLException e) { 48 | throw new SapcaiException("Invalid URL", e); 49 | } 50 | } 51 | 52 | /** 53 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format. 54 | * @param myfile The name of the file 55 | * @return The Response corresponding to your input 56 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file 57 | */ 58 | public Response doFileRequest (String myfile) throws SapcaiException { 59 | return new Response(this.sendAudioFile(myfile, this.token, this.language)); 60 | } 61 | 62 | public Conversation doTextConverse (String myText) { 63 | URL obj; 64 | try { 65 | obj = new URL(converseAPI); 66 | String sapcaiJson = this.doApiRequest(myText, this.token, this.language, obj); 67 | return new Conversation(sapcaiJson, this.token); 68 | } catch (MalformedURLException e) { 69 | throw new SapcaiException("Invalid URL", e); 70 | } 71 | } 72 | 73 | private String sendAudioFile(String name, String token, String language) throws SapcaiException { 74 | String sapcaiJson = ""; 75 | StringBuilder sb; 76 | try { 77 | MultipartUtility multipart = new MultipartUtility(sapcaiAPI, "UTF-8", token); 78 | File f = new File(name); 79 | if (!f.exists()) { 80 | throw new SapcaiException("File not found: " + name); 81 | } 82 | multipart.addFilePart("voice", f); 83 | if (language != null) { 84 | multipart.addFormField("language", language); 85 | } else if (this.language != null) { 86 | multipart.addFormField("language", this.language); 87 | } 88 | List response = multipart.finish(); 89 | sb = new StringBuilder(response.size() * 2); 90 | for (String line : response) { 91 | sb.append(line); 92 | } 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | throw new SapcaiException("Error during request", e); 96 | } 97 | return sb.toString(); 98 | } 99 | 100 | public String doApiRequest(String text, String token, String language, URL obj) throws SapcaiException { 101 | // URL obj; 102 | HttpsURLConnection con; 103 | OutputStream os; 104 | int responseCode; 105 | String inputLine; 106 | StringBuffer responseBuffer; 107 | String sapcaiJson; 108 | 109 | try { 110 | // obj = new URL(sapcaiAPI); 111 | con = (HttpsURLConnection) obj.openConnection(); 112 | con.setRequestMethod("POST"); 113 | con.setRequestProperty("Authorization", "Token " + token); 114 | con.setDoOutput(true); 115 | text = "text=" + text; 116 | os = con.getOutputStream(); 117 | os.write(text.getBytes()); 118 | if (language != null) { 119 | String l = "&language=" + language; 120 | os.write(l.getBytes()); 121 | } 122 | os.flush(); 123 | os.close(); 124 | 125 | responseCode = con.getResponseCode(); 126 | } catch (MalformedURLException e) { 127 | throw new SapcaiException("Invalid URL", e); 128 | } catch (IOException e) { 129 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 130 | } 131 | 132 | if (responseCode == HttpsURLConnection.HTTP_OK) { 133 | try { 134 | BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream())); 135 | responseBuffer = new StringBuffer(); 136 | while ((inputLine = reader.readLine()) != null) { 137 | responseBuffer.append(inputLine); 138 | } 139 | reader.close(); 140 | } catch (IOException e) { 141 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 142 | } 143 | sapcaiJson = responseBuffer.toString(); 144 | } else { 145 | System.out.println(responseCode); 146 | throw new SapcaiException(responseCode); 147 | } 148 | return sapcaiJson; 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/test/java/ai/sapcai/sdk_android/SdkTests.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import static org.mockito.Mockito.*; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.exceptions.misusing.UnfinishedStubbingException; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | import static org.junit.Assert.*; 10 | 11 | @RunWith(MockitoJUnitRunner.class) 12 | public class SdkTests { 13 | static final String FAKE_JSON = "{" + 14 | " \"results\": {" + 15 | " \"source\": \"What is the weather in London tomorrow? And in Paris?\"," + 16 | " \"intents\": [" + 17 | " {" + 18 | " \"name\": \"weather\"," + 19 | " \"confidence\": 0.88" + 20 | " }" + 21 | " ]," + 22 | " \"act\": \"wh-query\"," + 23 | " \"type\": \"desc:desc\"," + 24 | " \"sentiment\": \"neutral\"," + 25 | " \"entities\": {" + 26 | " \"action\": [" + 27 | " {" + 28 | " \"agent\": \"the weather in london\"," + 29 | " \"tense\": \"present\"," + 30 | " \"raw\": \"is\"," + 31 | " \"confidence\": 0.97" + 32 | " }" + 33 | " ]," + 34 | " \"location\": [" + 35 | " {" + 36 | " \"formated\": \"London, london, Greater London, England, United Kingdom\"," + 37 | " \"lng\": -0.1277583," + 38 | " \"lat\": 51.5073509," + 39 | " \"raw\": \"London\"," + 40 | " \"confidence\": 0.97" + 41 | " }," + 42 | " {" + 43 | " \"formated\": \"Paris, Paris, Ile-de-France, France\"," + 44 | " \"lng\": 2.3522219," + 45 | " \"lat\": 48.856614," + 46 | " \"raw\": \"Paris\"," + 47 | " \"confidence\": 0.83" + 48 | " }" + 49 | " ]," + 50 | " \"datetime\": [" + 51 | " {" + 52 | " \"value\": \"2016-07-11T10:00:00+00:00\"," + 53 | " \"raw\": \"tomorrow\"," + 54 | " \"confidence\": 0.83" + 55 | " }" + 56 | " ]" + 57 | " }," + 58 | " \"language\": \"en\"," + 59 | " \"processing_language\": \"en\"," + 60 | " \"version\": \"2.0.0\"," + 61 | " \"timestamp\": \"\"," + 62 | " \"uuid\": \"34b3f548-4aaf-4e3a-add1-f8f29f30e7fb\"," + 63 | " \"status\": 200" + 64 | " }," + 65 | " \"message\": \"Requests rendered with success\"" + 66 | "}"; 67 | 68 | static final String INVALID_JSON = "{" + 69 | "},"; 70 | @Mock 71 | Client c = new Client("token", "en"); 72 | 73 | @Test 74 | public void testResponseGetters() { 75 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON)); 76 | Response r = c.textRequest("text"); 77 | 78 | assertTrue(r != null); 79 | assertTrue(r.getSource().equals("What is the weather in London tomorrow? And in Paris?")); 80 | assertTrue(r.getVersion().equals("2.0.0")); 81 | assertTrue(r.getRaw().equals(FAKE_JSON)); 82 | 83 | assertTrue(r.getAct().equals(Response.ACT_WH_QUERY)); 84 | assertTrue(r.getType().equals("desc:desc")); 85 | assertTrue(r.getSentiment().equals(Response.SENTIMENT_NEUTRAL)); 86 | 87 | assertTrue(r.getEntities("location").length == 2); 88 | assertTrue(r.getEntities("datetime").length == 1); 89 | assertTrue(r.getEntities("fakename") == null); 90 | assertTrue(r.getEntity("fakename") == null); 91 | assertTrue(r.getStatus() == 200); 92 | assertTrue(r.getLanguage().equals("en")); 93 | assertTrue(r.getProcessingLanguage().equals("en")); 94 | assertTrue(r.getUuid().equals("34b3f548-4aaf-4e3a-add1-f8f29f30e7fb")); 95 | 96 | assertTrue(r.getIntents().length == 1); 97 | } 98 | 99 | @Test 100 | public void testResponseHelpers() { 101 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON)); 102 | Response r = c.textRequest("text"); 103 | 104 | assertTrue(r.isWhQuery()); 105 | 106 | assertTrue(r != null); 107 | assertFalse(r.isAssert()); 108 | assertFalse(r.isCommand()); 109 | assertFalse(r.isYesNoQuery()); 110 | 111 | assertTrue(r.isDescription()); 112 | 113 | assertFalse(r.isPositive()); 114 | assertFalse(r.isNegative()); 115 | assertTrue(r.isNeutral()); 116 | assertFalse(r.isVeryPositive()); 117 | assertFalse(r.isVeryNegative()); 118 | 119 | assertFalse(r.isAbbreviation()); 120 | assertFalse(r.isEntity()); 121 | assertFalse(r.isHuman()); 122 | assertFalse(r.isLocation()); 123 | assertFalse(r.isNumber()); 124 | } 125 | 126 | @Test 127 | public void testEntities() { 128 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON)); 129 | Response r = c.textRequest("text"); 130 | 131 | Entity london = r.getEntity("location"); 132 | assertTrue(london.getName().equals("location")); 133 | assertTrue((Double)london.getField("lng") == -0.1277583); 134 | assertTrue((Double)london.getField("lat") == 51.5073509); 135 | assertTrue(london.getConfidence() == 0.97); 136 | } 137 | 138 | @Test 139 | public void testIntent() { 140 | when(c.textRequest("text")).thenReturn(new Response(FAKE_JSON)); 141 | Response r = c.textRequest("text"); 142 | 143 | Intent i = r.getIntent(); 144 | assertTrue(i.getConfidence() == 0.88); 145 | assertTrue(i.getName().equals("weather")); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Response.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.json.*; 4 | 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.HashMap; 8 | import java.util.regex.Pattern; 9 | import java.util.regex.Matcher; 10 | 11 | /** 12 | * The Response class handles responses from SAP Conversational AI API 13 | * 14 | * @author Francois Triquet 15 | * @version 2.0.0 16 | * @since 2016-05-17 17 | * 18 | */ 19 | public class Response { 20 | private String source; 21 | private Intent[] intents; 22 | private Map entities; 23 | private String version; 24 | private String timestamp; 25 | private String raw; 26 | private int status; 27 | private String language; 28 | private String processing_language; 29 | private String uuid; 30 | 31 | 32 | private String sentiment; 33 | private String act; 34 | private String type; 35 | private String subtype; 36 | 37 | public static final String ACT_ASSERT = "assert"; 38 | public static final String ACT_COMMAND = "command"; 39 | public static final String ACT_WH_QUERY = "wh-query"; 40 | public static final String ACT_YN_QUERY = "yn-query"; 41 | 42 | public static final String TYPE_ABBREVIATION = "abbr:"; 43 | public static final String TYPE_ENTITY = "enty:"; 44 | public static final String TYPE_DESCRIPTION = "desc:"; 45 | public static final String TYPE_HUMAN = "hum:"; 46 | public static final String TYPE_NUMBER = "loc:"; 47 | public static final String TYPE_LOCATION = "num:"; 48 | 49 | public static final String SENTIMENT_POSITIVE = "positive"; 50 | public static final String SENTIMENT_VERY_POSITIVE = "vpositive"; 51 | public static final String SENTIMENT_NEGATIVE = "negative"; 52 | public static final String SENTIMENT_VERY_NEGATIVE = "vnegative"; 53 | public static final String SENTIMENT_NEUTRAL = "neutral"; 54 | 55 | 56 | Response(String json) throws SapcaiException { 57 | JSONArray resultIntents = null; 58 | JSONObject result; 59 | Pattern typePattern; 60 | this.raw = json; 61 | 62 | try { 63 | result = new JSONObject(json).getJSONObject("results"); 64 | 65 | this.source = result.getString("source"); 66 | this.version = result.getString("version"); 67 | this.timestamp = result.getString("timestamp"); 68 | this.status = result.getInt("status"); 69 | this.language = result.getString("language"); 70 | this.processing_language = result.getString("processing_language"); 71 | this.type = result.optString("type", null); 72 | this.act = result.getString("act"); 73 | this.sentiment = result.getString("sentiment"); 74 | this.uuid = result.getString("uuid"); 75 | 76 | 77 | JSONObject resultEntities = result.optJSONObject("entities"); 78 | this.entities = new HashMap(); 79 | if (resultEntities.length() != 0) { 80 | Iterator it = resultEntities.keys(); 81 | 82 | while (it.hasNext()) { 83 | String entityName = it.next(); 84 | JSONArray entity = resultEntities.optJSONArray(entityName); 85 | 86 | Entity[] values = new Entity[entity.length()]; 87 | for (int i = 0; i < values.length; i++) { 88 | values[i] = new Entity(entityName, entity.optJSONObject(i)); 89 | } 90 | this.entities.put(entityName, values); 91 | } 92 | } 93 | 94 | resultIntents = result.getJSONArray("intents"); 95 | this.intents = new Intent[resultIntents.length()]; 96 | for (int i = 0; i < this.intents.length; ++i) { 97 | this.intents[i] = new Intent(resultIntents.getJSONObject(i)); 98 | } 99 | 100 | if(this.type != null){ 101 | typePattern = Pattern.compile("(\\w+:).*"); 102 | Matcher m = typePattern.matcher(this.type); 103 | if (m.find()) { 104 | this.subtype = m.group(1); 105 | } else { 106 | this.subtype = this.type; 107 | } 108 | } 109 | } catch (Exception e) { 110 | throw new SapcaiException("Invalid JSON", e); 111 | } 112 | } 113 | 114 | /** 115 | * Returns the user input 116 | * @return The input sent to SAP Conversational AI 117 | */ 118 | public String getSource() { 119 | return this.source; 120 | } 121 | 122 | /** 123 | * Returns the uuid of the response 124 | * @return The uuid of the response 125 | */ 126 | public String getUuid() { return this.uuid; } 127 | 128 | /** 129 | * Returns the intent that matches with the input or null otherwise 130 | * @return The matched intent 131 | */ 132 | public Intent getIntent() { 133 | if (this.intents.length > 0) { 134 | return this.intents[0]; 135 | } 136 | return null; 137 | } 138 | 139 | public String getLanguage() { 140 | return this.language; 141 | } 142 | 143 | public String getProcessingLanguage() { 144 | return this.processing_language; 145 | } 146 | 147 | /** 148 | * Returns an array of all the intents, ordered by propability 149 | * @return All matched intents 150 | */ 151 | public Intent[] getIntents() { 152 | return this.intents; 153 | } 154 | 155 | /** 156 | * Returns the json received from SAP Conversational AI 157 | * @return The raw json string 158 | */ 159 | public String getRaw() { 160 | return this.raw; 161 | } 162 | 163 | 164 | /** 165 | * Returns the status of the request 166 | * @return The request status 167 | */ 168 | public int getStatus() { 169 | return this.status; 170 | } 171 | 172 | /** 173 | * Returns the version of the JSON 174 | * @return The SAP Conversational AI version 175 | */ 176 | public String getVersion() { 177 | return this.version; 178 | } 179 | 180 | /** 181 | * Returns the timestamp of the request 182 | * @return The timestampt of the request 183 | */ 184 | public String getTimestamp() { 185 | return this.timestamp; 186 | } 187 | 188 | /** 189 | * Returns the first entity that matches with the parameter if ther is one, or null otherwise 190 | * @param name The name of the entity 191 | * @return The entity matched or null 192 | * @see Entity 193 | */ 194 | public Entity getEntity(String name) { 195 | Entity e; 196 | 197 | Entity[] ents = this.entities.get(name); 198 | if (ents == null || ents.length == 0) { 199 | return null; 200 | } 201 | return ents[0]; 202 | } 203 | 204 | /** 205 | * Returns an array of all the entities in the input if there are some, or null otherwise 206 | * @param name The name of the Entity 207 | * @return An array of entities 208 | * @see Entity 209 | */ 210 | public Entity[] getEntities(String name) { 211 | return this.entities.get(name); 212 | } 213 | 214 | public String getAct() { 215 | return act; 216 | } 217 | 218 | public String getType() { 219 | return type; 220 | } 221 | 222 | public String getSentiment() { 223 | return sentiment; 224 | } 225 | 226 | public boolean isCommand() { 227 | return this.act.equals(ACT_COMMAND); 228 | } 229 | 230 | public boolean isAssert() { 231 | return this.act.equals(ACT_ASSERT); 232 | } 233 | 234 | public boolean isWhQuery() { 235 | return this.act.equals(ACT_WH_QUERY); 236 | } 237 | 238 | public boolean isYesNoQuery() { 239 | return this.act.equals(ACT_YN_QUERY); 240 | } 241 | 242 | public boolean isPositive() { return this.sentiment.equals(SENTIMENT_POSITIVE); } 243 | 244 | public boolean isVeryPositive() { 245 | return this.sentiment.equals(SENTIMENT_VERY_POSITIVE); 246 | } 247 | 248 | public boolean isNeutral() { 249 | return this.sentiment.equals(SENTIMENT_NEUTRAL); 250 | } 251 | 252 | public boolean isNegative() { 253 | return this.sentiment.equals(SENTIMENT_NEGATIVE); 254 | } 255 | 256 | public boolean isVeryNegative() { 257 | return this.sentiment.equals(SENTIMENT_VERY_NEGATIVE); 258 | } 259 | 260 | public boolean isAbbreviation() { 261 | return this.subtype.equals(TYPE_ABBREVIATION); 262 | } 263 | 264 | public boolean isEntity() { return this.subtype.equals(TYPE_ENTITY); } 265 | 266 | public boolean isDescription() { return this.subtype.equals(TYPE_DESCRIPTION); } 267 | 268 | public boolean isHuman() { 269 | return this.subtype.equals(TYPE_HUMAN); 270 | } 271 | 272 | public boolean isLocation() { 273 | return this.subtype.equals(TYPE_LOCATION); 274 | } 275 | 276 | public boolean isNumber() { 277 | return this.subtype.equals(TYPE_NUMBER); 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SAP Conversational AI - SDK-Android 2 | ![SAP Conversational AI Logo](https://cdn.cai.tools.sap/brand/sapcai/sap-cai-black.svg) 3 | 4 | # 🚨 Sunset of Open Source SDKs for SAP Conversational AI 5 | 6 | SAP Conversational AI provides several SDKs, which are all open-source and hosted on GitHub. 7 | Starting from January 2021, please note that we inform you that the SDKs will not be available anymore and the public repository of the project will be archived from GitHub. 8 | 9 | ## ✨ Why are we sunsetting our SDKs? 10 | 11 | Firstly, we noticed over the past year that these SDKs were not used much by our users. 12 | This is because our platform usage has become easier, including the APIs. 13 | 14 | Secondly, our APIs have undergone major changes. We would need to adapt the SDKs in order to keep them working, which will lead to a significant cost from our side. 15 | 16 | Hence, we decided to sunset this open source version starting from January 2021. 17 | 18 | ## ✨ What does it mean for me as a user? 19 | 20 | Any changes in our API’s will not be reflected in our SDKs. Hence, the code might not work unless you adjust the same. 21 | 22 | ## ✨ What are the next steps? 23 | 24 | If you are interested in taking the ownership of the project on GitHub, please get in touch with us and we can discuss the process. Otherwise, if there are no objections from anyone, we would archive the project following the open source sunset process. 25 | 26 | Please use the platform SAP Answers if you have any other questions related to this topic. 27 | 28 | Happy bot building! 29 | 30 | The SAP Conversational AI team 31 | 32 | --- 33 | 34 | 35 | ## Synopsis 36 | This module is an Android interface to the [SAP Conversational AI](https://cai.tools.sap) API. It allows you to make requests to your bots. 37 | 38 | ## Installation 39 | Just update your build.gradle files: 40 | 41 | In your module: 42 | ```gradle 43 | dependencies { 44 | compile 'ai.sapcai.sdk_android:sdk-android:4.0.0' 45 | } 46 | ``` 47 | 48 | ## Usage 49 | ```java 50 | import ai.sapcai.sdk_android.Client; 51 | 52 | Client client = new Client(YOUR_TOKEN); 53 | Response resp; 54 | 55 | try { 56 | resp = client.textRequest(YOUR_TEXT); 57 | } catch (SapcaiException e) { 58 | // error 59 | } 60 | ``` 61 | 62 | ## Specs 63 | ### Classes 64 | 65 | This module contains 9 main classes as follows: 66 | * Client is the client allowing you to make requests to SAP Conversational AI API. 67 | * Response contains the response from [SAP Conversational AI](https://cai.tools.sap). 68 | * Entity represents an entity found in you user's input as defined in [SAP Conversational AI Manual](https://cai.tools.sap/docs/#list-of-entities) 69 | * Intent represents the intent of your user 70 | * Action represents the actions of a conversation 71 | * Memory represents the memory of a conversation 72 | * MemoryEntity represents the entity inside the memory object 73 | * Conversation allowing you to handle a conversation 74 | * SapcaiException is the error thrown by the module. 75 | 76 | ### Class Client 77 | The SAP Conversational AI Client can be instanciated with a token and provides the following methods: 78 | 79 | #### Request: 80 | * textRequest(String text) 81 | * fileRequest(String filename) 82 | 83 | These methods both return a Response object. 84 | 85 | #### Conversation 86 | * textConverse(String text) 87 | 88 | This method return a Conversation object. 89 | 90 | #### Audio Recording: 91 | * startRecording() *Starts the audio recording to a file* 92 | * stopRecording() *Stops the audio recording, sends the audio to SAP Conversational AI and returns a Response object* 93 | 94 | Note that all these methods should be called in separated tasks because they do http requests. 95 | 96 | 97 | ```java 98 | import ai.sapcai.sdk_android.Client; 99 | 100 | Client client = new Client(YOUR_TOKEN); 101 | Response resp; 102 | 103 | try { 104 | resp = client.textRequest("Hello World!"); 105 | // Do your code... 106 | } catch (Exception e) { 107 | // Handle error 108 | } 109 | 110 | try { 111 | resp = client.fileRequest("my_file.wav"); 112 | // Do you code.. 113 | } catch (Exception e) { 114 | // Handle error 115 | } 116 | 117 | try { 118 | Conversation conversation = client.textConverse("Hello, my name is Paul"); 119 | } catch (Exception e) { 120 | // Handle error 121 | } 122 | ``` 123 | 124 | ### Class Response 125 | The SAP Conversational AI Response is generated after a call with the Client methods and contains the following methods: 126 | * getAct() *Returns the act of the sentence* 127 | * getType() *Returns the type of the sentence* 128 | * getSentiment() *Returns the sentiment of the sentence* 129 | * getEntity(String name) *Returns the first entity matching -name- or null* 130 | * getEntities(String name) *Returns an array of all entities matching -name- or null* 131 | * getStatus() *Returns the status of the Response* 132 | * getSource() *Returns the source of the input* 133 | * getIntent() *Returns the main intent detected by SAP Conversational AI* 134 | * getIntents() *Returns all intents ordered by probability* 135 | * getVersion() *Returns the version of the JSON* 136 | * getUuid() *Returns the uuid of the response* 137 | * IsAbbreviation() *IsAbbreviation returns whether or not the sentence is asking for an abbreviation* 138 | * IsEntity() *IsEntity returns whether or not the sentence is asking for an entity* 139 | * IsDescription() *IsDescription returns whether or not the sentence is asking for an description* 140 | * IsHuman() *IsHuman returns whether or not the sentence is asking for an human* 141 | * IsLocation() *IsLocation returns whether or not the sentence is asking for an location* 142 | * IsNumber() *IsNumber returns whether or not the sentence is asking for an number* 143 | * IsPositive() *IsPositive returns whether or not the sentiment is positive* 144 | * IsVeryPositive() *IsVeryPositive returns whether or not the sentiment is very positive* 145 | * IsNeutral() *IsNeutral returns whether or not the sentiment is neutral* 146 | * IsNegative() *IsNegative returns whether or not the sentiment is negative* 147 | * IsVeryNegative() *IsVeryNegative returns whether or not the sentiment is very negative* 148 | * IsAssert() *IsAssert returns whether or not the sentence is an assertion* 149 | * IsCommand() *IsCommand returns whether or not the sentence is a command* 150 | * IsWhQuery() *IsWhQuery returns whether or not the sentence is a wh query* 151 | * IsYnQuery() *IsYnQuery returns whether or not the sentence is a yes-no question* 152 | * Get(name string) *Returns the first entity matching -name-* 153 | * All(name string) *Returns all entities matching -name-* 154 | 155 | 156 | ```java 157 | resp = client.textRequest("Give me a recipe with Asparagus."); 158 | String intent = resp.getIntent(); 159 | if (intent != null && intent.equals("recipe")) { 160 | //get all the entities matching 'ingredient' 161 | Entities[] entities = resp.getEntities("ingredient"); 162 | 163 | // ... 164 | } 165 | ``` 166 | 167 | ### Class Entity 168 | The SAP Conversational AI Entity is returned by Response and provides the following methods: 169 | * String getName() *Returns the name of the entity* 170 | * String getRaw() *Returns the raw text on which the entity was detected* 171 | * Object getField(String fieldName) 172 | 173 | 174 | In addition to getName and getRaw, more attributes can be accessed by the getField method which can be one of the following: 175 | * hex 176 | * value 177 | * deg 178 | * formatted 179 | * lng 180 | * lat 181 | * unit 182 | * code 183 | * person 184 | * number 185 | * gender 186 | * next 187 | * grain 188 | * order 189 | 190 | SAP Conversational AI entity fields types are dependant on the entity itself, so value returned must be casted depending on the entity you wait. 191 | 192 | Refer to [SAP Conversational AI Entities Manual](https://cai.tools.sap/docs/#list-of-entities) for details about entities 193 | 194 | ```java 195 | Response resp = client.textRequest("What's the weather in San Francisco?"); 196 | if (resp.getIntent() != null && resp.getIntent().equals("weather")) { 197 | Entity e = resp.getEntity("location"); 198 | if (e != null) { 199 | System.out.printf("You asked me for a weather in %s\n", (String)e.getField("formated")); 200 | } 201 | } 202 | ``` 203 | 204 | ### Class SapcaiException 205 | This exception is thrown when an error occurs during the request 206 | 207 | # More 208 | 209 | You can view the whole API reference at [cai.tools.sap/docs/api-reference](https://cai.tools.sap/docs/api-reference). 210 | 211 | You can follow us on Twitter at [@sapcai](https://twitter.com/sapcai) for updates and releases. 212 | 213 | ## License 214 | 215 | Copyright (c) [2019] [SAP Conversational AI](https://cai.tools.sap) 216 | 217 | Permission is hereby granted, free of charge, to any person obtaining a copy 218 | of this software and associated documentation files (the "Software"), to deal 219 | in the Software without restriction, including without limitation the rights 220 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 221 | copies of the Software, and to permit persons to whom the Software is 222 | furnished to do so, subject to the following conditions: 223 | 224 | The above copyright notice and this permission notice shall be included in all 225 | copies or substantial portions of the Software. 226 | 227 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 228 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 229 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 230 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 231 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 232 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 233 | SOFTWARE. 234 | -------------------------------------------------------------------------------- /sdk-android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Client.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | 4 | import android.os.Environment; 5 | import java.net.URL; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.OutputStream; 11 | import javax.net.ssl.HttpsURLConnection; 12 | import java.net.MalformedURLException; 13 | import java.io.File; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * The Client class handles requests to SAP Conversational AI API. 20 | * Note that requests methods and stopRecording should not be called in the main thread of your application because they process http requests. 21 | * 22 | * @author Francois Triquet 23 | * @version 2.0.0 24 | * @since 2016-05-17 25 | * 26 | */ 27 | public class Client { 28 | private static final String sapcaiAPI = "https://api.cai.tools.sap/v2/request"; 29 | private String token; 30 | private String language; 31 | private SapcaiRecorder recorder; 32 | 33 | public Request request; 34 | 35 | /** 36 | * Initialize a SAP Conversational AI Client with a authentication token 37 | * @param token Your token from SAP Conversational AI 38 | */ 39 | public Client(String token) { 40 | this.token = token; 41 | this.language = null; 42 | this.recorder = null; 43 | 44 | this.request = new Request(token); 45 | } 46 | 47 | public Client(String token, String language) { 48 | this.token = token; 49 | this.language = language; 50 | this.recorder = null; 51 | 52 | this.request = new Request(token, language); 53 | } 54 | 55 | /** 56 | * Initialize a SAP Conversational AI Client without authentication token 57 | */ 58 | public Client() { 59 | this("", null); 60 | } 61 | 62 | /** 63 | * Sets the token of the Client 64 | * @param token The token to authenticate to SAP Conversational AI 65 | */ 66 | public void setToken(String token) { 67 | this.token = token; 68 | } 69 | 70 | /** 71 | * Sets the language of the Client 72 | * @param language The language for the language processing 73 | */ 74 | public void setLanguage(String language) { 75 | this.language = language; 76 | } 77 | 78 | private static String getOutputFile() { 79 | File sapcaiDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/SAPConversationalAI"); 80 | if (!sapcaiDir.exists()) 81 | sapcaiDir.mkdir(); 82 | String filepath = Environment.getExternalStorageDirectory().getAbsolutePath(); 83 | return filepath + "/SAPConversationalAI/sapcai_audio.wav"; 84 | } 85 | 86 | /** 87 | * Starts recording audio from the microphone. Note that the audio must be shorter than 10 seconds to be processed by SAP Conversational AI 88 | * @throws SapcaiException if the client is already recording (the recording will stop) 89 | */ 90 | public synchronized void startRecording() throws SapcaiException { 91 | if (recorder != null) { 92 | try { 93 | this.stopRecording(); 94 | } catch (Exception ignore) {} 95 | throw new SapcaiException("Invalid recording state"); 96 | } 97 | recorder = new SapcaiRecorder(getOutputFile()); 98 | recorder.startRecording(); 99 | } 100 | 101 | /** 102 | * Stops recording from the microphone and returns the Response corresponding to the audio input after beeing processed 103 | * @return A SAP Conversational AI Response 104 | * @throws SapcaiException if the Client is not recording of if an error occurs (invalid audio...) 105 | * @see Response 106 | */ 107 | public synchronized Response stopRecording() throws SapcaiException { 108 | Response r; 109 | 110 | if (this.recorder == null || !this.recorder.isRecording()) { 111 | throw new SapcaiException("Illegal recording state"); 112 | } 113 | try { 114 | recorder.stopRecording(); 115 | File f = new File(getOutputFile()); 116 | r = fileRequest(getOutputFile()); 117 | } catch (IOException e) { 118 | throw new SapcaiException("Unable to record audio", e); 119 | } finally { 120 | recorder = null; 121 | } 122 | return r; 123 | } 124 | 125 | /** 126 | * Performs a text request to SAP Conversational AI 127 | * @param text The text to be processed 128 | * @param options A map of parameters to the request. Parameters can be "token" and "language" 129 | * @return The Response corresponding to the input 130 | * @throws SapcaiException if SAP Conversational AI can't process the text 131 | * @see Response 132 | */ 133 | public Response textRequest(String text, Map options) throws SapcaiException { 134 | String sapcaiJson; 135 | String token; 136 | String language; 137 | 138 | token = options.get("token"); 139 | if (token == null) 140 | token = this.token; 141 | language = options.get("language"); 142 | if (language == null) 143 | language = this.language; 144 | sapcaiJson = this.doApiRequest(text, token, language); 145 | return new Response(sapcaiJson); 146 | } 147 | 148 | /** 149 | * Performs a text request to SAP Conversational AI with the token of the Client 150 | * @param text The text to be processed 151 | * @return The Response corresponding to the input 152 | * @throws SapcaiException if SAP Conversational AI can't process the text 153 | * @see Response 154 | */ 155 | public Response textRequest(String text) throws SapcaiException { 156 | Map params = new HashMap<>(); 157 | params.put("language", this.language); 158 | params.put("token", this.token); 159 | return this.textRequest(text, params); 160 | } 161 | 162 | 163 | 164 | private String sendAudioFile(String name, String token, String language) throws SapcaiException { 165 | String sapcaiJson = ""; 166 | StringBuilder sb; 167 | try { 168 | MultipartUtility multipart = new MultipartUtility(sapcaiAPI, "UTF-8", token); 169 | File f = new File(name); 170 | if (!f.exists()) { 171 | throw new SapcaiException("File not found: " + name); 172 | } 173 | multipart.addFilePart("voice", f); 174 | if (language != null) { 175 | multipart.addFormField("language", language); 176 | } else if (this.language != null) { 177 | multipart.addFormField("language", this.language); 178 | } 179 | List response = multipart.finish(); 180 | sb = new StringBuilder(response.size() * 2); 181 | for (String line : response) { 182 | sb.append(line); 183 | } 184 | } catch (Exception e) { 185 | e.printStackTrace(); 186 | throw new SapcaiException("Error during request", e); 187 | } 188 | return sb.toString(); 189 | } 190 | 191 | /** 192 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format. 193 | * @param filename The name of the file 194 | * @return The Response corresponding to your input 195 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file 196 | */ 197 | public Response fileRequest(String filename) throws SapcaiException { 198 | return new Response(this.sendAudioFile(filename, this.token, null)); 199 | } 200 | 201 | /** 202 | * Performs a voice file request to SAP Conversational AI. Note that the audio must not exceed 10 seconds and be in wav format. 203 | * @param filename The name of the file 204 | * @param options A map of parameters for the request. This map can contains "token" and/or "language".If a parameter is not provided, the request will use the token or language of the Client. I it has not language, the language used will be the default language of the corresponding bot 205 | * @return The Response corresponding to your input 206 | * @throws SapcaiException if the file is invalid or SAP Conversational AI can't process the file 207 | */ 208 | public Response fileRequest(String filename, Map options) throws SapcaiException { 209 | String token; 210 | String language; 211 | 212 | token = options.get("token"); 213 | if (token == null) 214 | token = this.token; 215 | language = options.get("language"); 216 | if (language == null) 217 | language = this.language; 218 | String resp = sendAudioFile(filename, token, language); 219 | return new Response(resp); 220 | } 221 | 222 | 223 | public String doApiRequest(String text, String token, String language) throws SapcaiException { 224 | URL obj; 225 | HttpsURLConnection con; 226 | OutputStream os; 227 | int responseCode; 228 | String inputLine; 229 | StringBuffer responseBuffer; 230 | String sapcaiJson; 231 | 232 | try { 233 | obj = new URL(sapcaiAPI); 234 | con = (HttpsURLConnection) obj.openConnection(); 235 | con.setRequestMethod("POST"); 236 | con.setRequestProperty("Authorization", "Token " + token); 237 | con.setDoOutput(true); 238 | text = "text=" + text; 239 | os = con.getOutputStream(); 240 | os.write(text.getBytes()); 241 | if (language != null) { 242 | String l = "&language=" + language; 243 | os.write(l.getBytes()); 244 | } 245 | os.flush(); 246 | os.close(); 247 | 248 | responseCode = con.getResponseCode(); 249 | } catch (MalformedURLException e) { 250 | throw new SapcaiException("Invalid URL", e); 251 | } catch (IOException e) { 252 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 253 | } 254 | 255 | if (responseCode == HttpsURLConnection.HTTP_OK) { 256 | try { 257 | BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream())); 258 | responseBuffer = new StringBuffer(); 259 | while ((inputLine = reader.readLine()) != null) { 260 | responseBuffer.append(inputLine); 261 | } 262 | reader.close(); 263 | } catch (IOException e) { 264 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 265 | } 266 | sapcaiJson = responseBuffer.toString(); 267 | } else { 268 | System.out.println(responseCode); 269 | throw new SapcaiException(responseCode); 270 | } 271 | return sapcaiJson; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/main/java/ai/sapcai/sdk_android/Conversation.java: -------------------------------------------------------------------------------- 1 | package ai.sapcai.sdk_android; 2 | 3 | import org.apache.http.HttpResponse; 4 | import org.apache.http.ParseException; 5 | import org.apache.http.client.methods.HttpPost; 6 | import org.apache.http.client.methods.HttpPut; 7 | import org.apache.http.entity.StringEntity; 8 | import org.apache.http.impl.client.CloseableHttpClient; 9 | import org.apache.http.impl.client.HttpClientBuilder; 10 | import org.apache.http.util.EntityUtils; 11 | import org.json.*; 12 | 13 | import java.util.Iterator; 14 | import java.util.Map; 15 | 16 | import java.io.IOException; 17 | import java.io.UnsupportedEncodingException; 18 | import java.util.HashMap; 19 | 20 | 21 | public class Conversation { 22 | 23 | private static final String converseAPI = "https://api.cai.tools.sap/v2/converse"; 24 | 25 | private String raw; // String: the raw unparsed json response ok 26 | private String uuid; // String: the universal unique id of the api call ok 27 | private String source; // String: the user input ok 28 | private String[] replies; // Array[String]: all the replies ok 29 | private Action action; // Object: the action of the conversation ok 30 | private String sentiment; // String: the sentiment of the input ok 31 | private Action[] nextActions; // Array[Object]: the next actions of the conversation ok 32 | private Memory memory; // Object: the memory of the conversation ok 33 | private Map entities; // Array[Entity]: the array of entities ok 34 | private Intent[] intents; // Array[Object]: all the matched intents ok 35 | private String conversationToken; // String: the conversation token ok 36 | private String language; // String: the language of the input ok 37 | private String processing_language; // String: the language of the input ok 38 | private String version; // String: the API version ok 39 | private String timestamp; // String: the timestamp at the end of the processing ok 40 | private int status; // String: the status of the response ok 41 | 42 | private String token; 43 | 44 | public static final String SENTIMENT_POSITIVE = "positive"; 45 | public static final String SENTIMENT_VERY_POSITIVE = "vpositive"; 46 | public static final String SENTIMENT_NEGATIVE = "negative"; 47 | public static final String SENTIMENT_VERY_NEGATIVE = "vnegative"; 48 | public static final String SENTIMENT_NEUTRAL = "neutral"; 49 | 50 | public Conversation(String json, String token) throws SapcaiException { 51 | this.setToken(token); 52 | JSONArray resultIntents = null; 53 | JSONArray resultNextActions = null; 54 | JSONArray resultReplies = null; 55 | JSONObject result; 56 | this.raw = json; 57 | 58 | try { 59 | result = new JSONObject(json).getJSONObject("results"); 60 | 61 | this.source = result.getString("source"); 62 | this.version = result.getString("version"); 63 | this.timestamp = result.getString("timestamp"); 64 | this.status = result.getInt("status"); 65 | this.language = result.getString("language"); 66 | this.processing_language = result.getString("processing_language"); 67 | this.sentiment = result.getString("sentiment"); 68 | this.uuid = result.getString("uuid"); 69 | this.conversationToken = result.getString("conversation_token"); 70 | 71 | JSONObject resultEntities = result.optJSONObject("entities"); 72 | this.entities = new HashMap(); 73 | if (resultEntities.length() != 0) { 74 | Iterator it = resultEntities.keys(); 75 | 76 | while (it.hasNext()) { 77 | String entityName = it.next(); 78 | JSONArray entity = resultEntities.optJSONArray(entityName); 79 | Entity[] values = new Entity[entity.length()]; 80 | for (int i = 0; i < values.length; i++) { 81 | values[i] = new Entity(entityName, entity.optJSONObject(i)); 82 | } 83 | this.entities.put(entityName, values); 84 | } 85 | } 86 | 87 | resultIntents = result.getJSONArray("intents"); 88 | this.intents = new Intent[resultIntents.length()]; 89 | for (int i = 0; i < this.intents.length; ++i) { 90 | this.intents[i] = new Intent(resultIntents.getJSONObject(i)); 91 | } 92 | 93 | this.action = new Action(result.getJSONObject("action")); 94 | 95 | resultNextActions = result.getJSONArray("next_actions"); 96 | this.nextActions = new Action[resultNextActions.length()]; 97 | for(int i = 0; i < this.nextActions.length; ++i){ 98 | this.nextActions[i] = new Action(resultNextActions.getJSONObject(i)); 99 | } 100 | 101 | this.memory = new Memory(result.getJSONObject("memory")); 102 | 103 | resultReplies = result.getJSONArray("replies"); 104 | this.replies = new String[resultReplies.length()]; 105 | for(int i = 0; i < this.replies.length; ++i){ 106 | this.replies[i] = resultReplies.optString(i); 107 | } 108 | 109 | } catch (Exception e) { 110 | throw new SapcaiException("Invalid JSON", e); 111 | } 112 | } 113 | 114 | public String reply(){ 115 | return this.replies[0]; 116 | } 117 | 118 | public Action nextAction(){ 119 | return this.nextActions[0]; 120 | } 121 | 122 | public String joinedReplies(){ 123 | String replies = ""; 124 | for(String rep : this.replies){ 125 | replies = replies + rep; 126 | } 127 | return replies; 128 | } 129 | 130 | public boolean isPositive() { return this.sentiment.equals(SENTIMENT_POSITIVE); } 131 | 132 | public boolean isVeryPositive() { 133 | return this.sentiment.equals(SENTIMENT_VERY_POSITIVE); 134 | } 135 | 136 | public boolean isNeutral() { 137 | return this.sentiment.equals(SENTIMENT_NEUTRAL); 138 | } 139 | 140 | public boolean isNegative() { 141 | return this.sentiment.equals(SENTIMENT_NEGATIVE); 142 | } 143 | 144 | public boolean isVeryNegative() { 145 | return this.sentiment.equals(SENTIMENT_VERY_NEGATIVE); 146 | } 147 | 148 | public void setMemoryEntity(String name, MemoryEntity newMemoryEntity){ 149 | this.memory.setMemory(name, newMemoryEntity); 150 | String memory = this.memory.convertMemory(); 151 | String body = "{\"conversation_token\":\"" + this.conversationToken + "\", \"memory\":" + memory + "}"; 152 | this.doApiRequest(body, 0); 153 | } 154 | 155 | public void resetMemory(){ 156 | this.memory.resetMemory(); 157 | String body = "{\"conversation_token\":\"" + this.conversationToken + "\","; 158 | body = body + "\"memory\":null}"; 159 | this.doApiRequest(body, 0); 160 | } 161 | 162 | public void resetConversation(){ 163 | String body = "{\"conversation_token\": \"" + this.conversationToken + "\"}"; 164 | this.doApiRequest(body, 1); 165 | } 166 | 167 | public String doApiRequest(String body, int putOrDelete){ 168 | System.out.println("body: " + body); 169 | String url = converseAPI; 170 | try { 171 | CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 172 | HttpDelete requestD = new HttpDelete(url); 173 | HttpPut request = new HttpPut(url); 174 | String json = null; 175 | if(putOrDelete == 1){ 176 | requestD.addHeader("Authorization", "Token " + this.token); 177 | HttpResponse result = httpClient.execute(requestD); 178 | json = EntityUtils.toString(result.getEntity(), "UTF-8"); 179 | } else { 180 | StringEntity params; 181 | params = new StringEntity(body); 182 | request.addHeader("Authorization", "Token " + this.token); 183 | request.setEntity(params); 184 | HttpResponse result = httpClient.execute(request); 185 | json = EntityUtils.toString(result.getEntity(), "UTF-8"); 186 | } 187 | return json; 188 | } catch (UnsupportedEncodingException e) { 189 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 190 | } catch (ParseException e) { 191 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 192 | } catch (IOException e) { 193 | throw new SapcaiException("Unable to read response from SAP Conversational AI", e); 194 | } 195 | 196 | } 197 | 198 | public String getRaw() { 199 | return raw; 200 | } 201 | 202 | public void setRaw(String raw) { 203 | this.raw = raw; 204 | } 205 | 206 | public String getUuid() { 207 | return uuid; 208 | } 209 | 210 | public void setUuid(String uuid) { 211 | this.uuid = uuid; 212 | } 213 | 214 | public String getSource() { 215 | return source; 216 | } 217 | 218 | public void setSource(String source) { 219 | this.source = source; 220 | } 221 | 222 | public String[] getReplies() { 223 | return replies; 224 | } 225 | 226 | public void setReplies(String[] replies) { 227 | this.replies = replies; 228 | } 229 | 230 | public Action getAction() { 231 | return action; 232 | } 233 | 234 | public void setAction(Action action) { 235 | this.action = action; 236 | } 237 | 238 | public Action[] getNextActions() { 239 | return nextActions; 240 | } 241 | 242 | public void setNextActions(Action[] nextActions) { 243 | this.nextActions = nextActions; 244 | } 245 | 246 | public Memory getMemory() { 247 | return memory; 248 | } 249 | 250 | public void setMemory(Memory memory) { 251 | this.memory = memory; 252 | } 253 | 254 | public Map getEntities() { 255 | return entities; 256 | } 257 | 258 | public void setEntities(Map entities) { 259 | this.entities = entities; 260 | } 261 | 262 | public Intent[] getIntents() { 263 | return intents; 264 | } 265 | 266 | public void setIntents(Intent[] intents) { 267 | this.intents = intents; 268 | } 269 | 270 | public String getConversationToken() { 271 | return conversationToken; 272 | } 273 | 274 | public void setConversationToken(String conversationToken) { 275 | this.conversationToken = conversationToken; 276 | } 277 | 278 | public String getLanguage() { 279 | return language; 280 | } 281 | 282 | public void setLanguage(String language) { 283 | this.language = language; 284 | } 285 | 286 | public String getVersion() { 287 | return version; 288 | } 289 | 290 | public void setVersion(String version) { 291 | this.version = version; 292 | } 293 | 294 | public String getTimestamp() { 295 | return timestamp; 296 | } 297 | 298 | public void setTimestamp(String timestamp) { 299 | this.timestamp = timestamp; 300 | } 301 | 302 | public int getStatus() { 303 | return status; 304 | } 305 | 306 | public void setStatus(int status) { 307 | this.status = status; 308 | } 309 | 310 | public String getToken() { 311 | return token; 312 | } 313 | 314 | public void setToken(String token) { 315 | this.token = token; 316 | } 317 | 318 | } 319 | --------------------------------------------------------------------------------