Merge "Shared state modified in two threads without synchronization"
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 485d65e..52dd427 100644
--- a/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -19,8 +19,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.android.sample.rcsclient"
-    android:versionCode="10"
-    android:versionName="1.0.9">
+    android:versionCode="11"
+    android:versionName="1.0.10">
 
     <uses-sdk
         android:minSdkVersion="30"
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
index 8f7e6a8..40a108d 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
@@ -21,6 +21,7 @@
 import android.database.Cursor;
 import android.graphics.Color;
 import android.graphics.Typeface;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -41,6 +42,9 @@
 import com.google.android.sample.rcsclient.util.ChatManager;
 import com.google.android.sample.rcsclient.util.ChatProvider;
 import com.google.android.sample.rcsclient.util.NumberUtils;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -176,11 +180,8 @@
                     if (TextUtils.isEmpty(mDestNumber)) {
                         Log.i(TAG, "Destination number is empty");
                     } else {
-                        ChatManager.getInstance(getApplicationContext(), mSubId).addNewMessage(
-                                mNewMessage.getText().toString(), ChatManager.SELF, mDestNumber);
-                        ChatManager.getInstance(getApplicationContext(), mSubId).sendMessage(
-                                mDestNumber, mNewMessage.getText().toString());
-                        mHandler.sendMessage(mHandler.obtainMessage(EMPTY_MSG));
+                        Log.i(TAG, "send message");
+                        sendChatMessage();
                     }
                 });
             });
@@ -192,6 +193,34 @@
         }
     }
 
+    private void sendChatMessage() {
+        Uri result = ChatManager.getInstance(getApplicationContext(), mSubId).addNewMessage(
+                mNewMessage.getText().toString(), ChatManager.SELF, mDestNumber);
+        String chatId = result.getPathSegments().get(1);
+        Futures.addCallback(
+                ChatManager.getInstance(getApplicationContext(),
+                        mSubId).sendMessage(
+                        mDestNumber,
+                        mNewMessage.getText().toString()),
+                new FutureCallback<Void>() {
+                    @Override
+                    public void onSuccess(Void param) {
+                        Log.i(TAG, "send chat msg successfully");
+                        ChatManager.getInstance(getApplicationContext(), mSubId).updateMsgResult(
+                                chatId, true);
+                    }
+
+                    @Override
+                    public void onFailure(Throwable t) {
+                        Log.i(TAG, "fail to send chat message:" + t);
+                        ChatManager.getInstance(getApplicationContext(), mSubId).updateMsgResult(
+                                chatId, false);
+                    }
+                },
+                MoreExecutors.directExecutor());
+        mHandler.sendMessage(mHandler.obtainMessage(EMPTY_MSG));
+    }
+
     private void initChatMessageLayout(Cursor cursor) {
         Log.i(TAG, "initChatMessageLayout");
         RelativeLayout rl = findViewById(R.id.relative_layout);
@@ -221,7 +250,8 @@
         lp.setMargins(0, MARGIN_SIZE, 0, 0);
         if (messageFromSelf(cursor)) {
             lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-            chatMsg.setBackgroundColor(Color.YELLOW);
+            int result = cursor.getInt(cursor.getColumnIndex(ChatProvider.RcsColumns.RESULT));
+            chatMsg.setBackgroundColor(result == 1 ? Color.GREEN : Color.RED);
         } else {
             lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
             chatMsg.setBackgroundColor(Color.LTGRAY);
@@ -243,7 +273,8 @@
             Cursor cursor = getContentResolver().query(ChatProvider.CHAT_URI,
                     new String[]{ChatProvider.RcsColumns.SRC_PHONE_NUMBER,
                             ChatProvider.RcsColumns.DEST_PHONE_NUMBER,
-                            ChatProvider.RcsColumns.CHAT_MESSAGE},
+                            ChatProvider.RcsColumns.CHAT_MESSAGE,
+                            ChatProvider.RcsColumns.RESULT},
                     ChatProvider.RcsColumns.SRC_PHONE_NUMBER + "=? OR "
                             + ChatProvider.RcsColumns.DEST_PHONE_NUMBER + "=?",
                     new String[]{mDestNumber, mDestNumber},
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
index 399a860..ed22f03 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
@@ -19,6 +19,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.net.Uri;
 import android.telephony.ims.ImsManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -36,6 +37,7 @@
 import com.google.android.sample.rcsclient.SessionStateCallback;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 
 import gov.nist.javax.sip.address.AddressFactoryImpl;
@@ -227,13 +229,14 @@
      * @param contact destination phone number.
      * @param message chat message.
      */
-    public void sendMessage(String contact, String message) {
+    public ListenableFuture<Void> sendMessage(String contact, String message) {
         SimpleChatSession chatSession = mContactSessionMap.get(contact);
         if (chatSession == null) {
             Log.i(TAG, "session is unavailable for contact = " + contact);
-            return;
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("Chat session does not exist"));
         }
-        chatSession.sendMessage(message);
+        return chatSession.sendMessage(message);
     }
 
     public boolean isRegistered() {
@@ -263,7 +266,7 @@
      * @param src source phone number.
      * @param dest destination phone number.
      */
-    public void addNewMessage(String message, String src, String dest) {
+    public Uri addNewMessage(String message, String src, String dest) {
         long currentTime = Instant.now().getEpochSecond();
         ContentValues contentValues = new ContentValues();
         contentValues.put(ChatProvider.RcsColumns.SRC_PHONE_NUMBER, src);
@@ -272,7 +275,7 @@
         contentValues.put(ChatProvider.RcsColumns.MSG_TIMESTAMP, currentTime);
         contentValues.put(ChatProvider.RcsColumns.IS_READ, Boolean.TRUE);
         // insert chat table
-        mContext.getContentResolver().insert(ChatProvider.CHAT_URI, contentValues);
+        Uri result = mContext.getContentResolver().insert(ChatProvider.CHAT_URI, contentValues);
 
         ContentValues summary = new ContentValues();
         summary.put(ChatProvider.SummaryColumns.LATEST_MESSAGE, message);
@@ -288,6 +291,17 @@
             summary.put(ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER, remoteNumber);
             mContext.getContentResolver().insert(ChatProvider.SUMMARY_URI, summary);
         }
+        return result;
+    }
+
+    /**
+     * Update MSRP chat message sent result.
+     */
+    public void updateMsgResult(String id, boolean success) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(ChatProvider.RcsColumns.RESULT, success);
+        mContext.getContentResolver().update(ChatProvider.CHAT_URI, contentValues,
+                ChatProvider.RcsColumns._ID + "=?", new String[]{id});
     }
 
     /**
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java
index 050da1f..98f3ceb 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
@@ -36,7 +37,7 @@
     public static final Uri SUMMARY_URI = Uri.parse("content://rcsprovider/summary");
     public static final String AUTHORITY = "rcsprovider";
     private static final String TAG = "TestRcsApp.ChatProvider";
-    private static final int DATABASE_VERSION = 1;
+    private static final int DATABASE_VERSION = 2;
     private static final String CHAT_TABLE_NAME = "chat";
     private static final String SUMMARY_TABLE_NAME = "summary";
 
@@ -146,9 +147,9 @@
         public static final String SRC_PHONE_NUMBER = "source_phone_number";
         public static final String DEST_PHONE_NUMBER = "destination_phone_number";
         public static final String CHAT_MESSAGE = "chat_message";
-        public static final String SUBSCRIPTION_ID = "subscription_id";
         public static final String MSG_TIMESTAMP = "msg_timestamp";
         public static final String IS_READ = "is_read";
+        public static final String RESULT = "result";
     }
 
     /** Define columns for the summary table. */
@@ -169,7 +170,9 @@
                 + RcsColumns.DEST_PHONE_NUMBER + " Text DEFAULT NULL, "
                 + RcsColumns.CHAT_MESSAGE + " Text DEFAULT NULL, "
                 + RcsColumns.MSG_TIMESTAMP + " LONG DEFAULT NULL, "
-                + RcsColumns.IS_READ + " BOOLEAN DEFAULT false);";
+                + RcsColumns.IS_READ + " BOOLEAN DEFAULT false, "
+                + RcsColumns.RESULT + " BOOLEAN DEFAULT true);";
+
         public static final String SQL_CREATE_SUMMARY_TABLE = "CREATE TABLE "
                 + SUMMARY_TABLE_NAME
                 + " ("
@@ -191,8 +194,32 @@
         }
 
         @Override
-        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.d(TAG, "DB upgrade from " + oldVersion + " to " + newVersion);
+            db.beginTransaction();
+            try {
+                switch (oldVersion) {
+                    case 1:
+                        upgradeDatabaseToVersion2(db);
+                        break;
+                    default: // fall out
+                }
+                db.setTransactionSuccessful();
+            } catch (Exception ex) {
+                Log.e(TAG, ex.getMessage(), ex);
+            } finally {
+                db.endTransaction();
+            }
+        }
+
+        private static void upgradeDatabaseToVersion2(SQLiteDatabase db) {
+            try {
+                Log.d(TAG, "upgradeDatabaseToVersion2");
+                String alterTable = "ALTER TABLE " + CHAT_TABLE_NAME + " ADD COLUMN ";
+                db.execSQL(alterTable + RcsColumns.RESULT + " BOOLEAN DEFAULT true");
+            } catch (SQLiteException e) {
+                Log.w(TAG, "[upgradeDatabaseToVersion10] Exception adding column: " + e);
+            }
         }
     }
 }