intent: support share with Ring

Change-Id: Ic6536bcb3c822a4c4c77194a9a012e321cfabde8
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 7ad2871..5181658 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -134,6 +134,37 @@
                 <data android:scheme="ring" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name=".client.ShareActivity"
+            android:theme="@style/AppThemeBase"
+            android:parentActivityName=".client.HomeActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.service.chooser.chooser_target_service"
+                android:value=".services.RingChooserTargetService" />
+        </activity>
+
         <activity
             android:name=".account.AccountWizardActivity"
             android:configChanges="screenSize|screenLayout|smallestScreenSize"
@@ -316,7 +347,17 @@
             android:name=".client.MediaViewerActivity"
             android:exported="false"
             android:label="@string/title_media_viewer"
-            android:theme="@style/AppThemeBase.Dark"></activity>
+            android:theme="@style/AppThemeBase.Dark" />
+
+        <service
+            android:name=".services.RingChooserTargetService"
+            android:label="ChooserTargetService"
+            android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.chooser.ChooserTargetService" />
+            </intent-filter>
+        </service>
+
     </application>
 
 </manifest>
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
index 3281d81..4e7a130 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
@@ -55,8 +55,9 @@
 
     public SmartListAdapter(List<SmartListViewModel> smartListViewModels, SmartListViewHolder.SmartListListeners listener) {
         this.listener = listener;
-        this.mSmartListViewModels = new ArrayList<>();
-        this.mSmartListViewModels.addAll(smartListViewModels);
+        mSmartListViewModels = new ArrayList<>();
+        if (smartListViewModels != null)
+            mSmartListViewModels.addAll(smartListViewModels);
     }
 
     @NonNull
diff --git a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
index 8e9c407..7d25d09 100644
--- a/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
+++ b/ring-android/app/src/main/java/cx/ring/application/RingApplication.java
@@ -32,6 +32,8 @@
 import android.os.Looper;
 import android.support.v7.app.AppCompatDelegate;
 
+import com.bumptech.glide.Glide;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Future;
@@ -129,6 +131,7 @@
     public void onLowMemory() {
         super.onLowMemory();
         AvatarFactory.onLowMemory();
+        Glide.get(this).clearMemory();
     }
 
     private void setDefaultUncaughtExceptionHandler() {
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
index b410b4d..480f9c1 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
@@ -20,6 +20,7 @@
  */
 package cx.ring.client;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
@@ -41,6 +42,8 @@
     private String contactUri = null;
     private String accountId = null;
 
+    private Intent mPendingIntent = null;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -54,9 +57,12 @@
 
         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 
-        if (getIntent() != null || getIntent().getExtras() != null) {
-            contactUri = getIntent().getStringExtra(ConversationFragment.KEY_CONTACT_RING_ID);
-            accountId = getIntent().getStringExtra(ConversationFragment.KEY_ACCOUNT_ID);
+        Intent intent = getIntent();
+        String action = intent == null ? null : intent.getAction();
+
+        if (intent != null) {
+            contactUri = intent.getStringExtra(ConversationFragment.KEY_CONTACT_RING_ID);
+            accountId = intent.getStringExtra(ConversationFragment.KEY_ACCOUNT_ID);
         } else if (savedInstanceState != null) {
             contactUri = savedInstanceState.getString(ConversationFragment.KEY_CONTACT_RING_ID);
             accountId = savedInstanceState.getString(ConversationFragment.KEY_ACCOUNT_ID);
@@ -72,6 +78,29 @@
                     .replace(R.id.main_frame, mConversationFragment, null)
                     .commit();
         }
+        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            mPendingIntent = intent;
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        if (mPendingIntent != null) {
+            handleShareIntent(mPendingIntent);
+            mPendingIntent = null;
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleShareIntent(intent);
+    }
+
+    private void handleShareIntent(Intent intent) {
+        if (mConversationFragment != null)
+            mConversationFragment.handleShareIntent(intent);
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
index f852338..826fad0 100644
--- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
@@ -36,6 +36,8 @@
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
+
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.MenuItem;
 import android.view.View;
@@ -201,14 +203,16 @@
         // if app opened from notification display trust request fragment when mService will connected
         Intent intent = getIntent();
         Bundle extra = intent.getExtras();
-        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT.equals(intent.getAction())) {
+        String action = intent.getAction();
+        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT.equals(action)) {
             if (extra == null || extra.getString(ContactRequestsFragment.ACCOUNT_ID) == null) {
                 return;
             }
             mAccountWithPendingrequests = extra.getString(ContactRequestsFragment.ACCOUNT_ID);
+        } else if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            handleShareIntent(intent);
         }
 
-
         setVideoEnabledFromPermission();
 
         FragmentManager fragmentManager = getFragmentManager();
@@ -230,19 +234,38 @@
 
     }
 
+    private void handleShareIntent(Intent intent) {
+        String action = intent.getAction();
+        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            Bundle extra = intent.getExtras();
+            if (extra != null) {
+                String accountId = extra.getString(ConversationFragment.KEY_ACCOUNT_ID);
+                String uri = extra.getString(ConversationFragment.KEY_CONTACT_RING_ID);
+                if (!TextUtils.isEmpty(accountId) && !TextUtils.isEmpty(uri)) {
+                    intent.setClass(this, ConversationActivity.class);
+                    startActivity(intent);
+                }
+            }
+        }
+    }
+
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         Log.d(TAG, "onNewIntent: " + intent);
-        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT.equals(intent.getAction())) {
+        String action = intent.getAction();
+        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT.equals(action)) {
             Bundle extra = intent.getExtras();
             if (extra == null || extra.getString(ContactRequestsFragment.ACCOUNT_ID) == null) {
                 return;
             }
             presentTrustRequestFragment(extra.getString(ContactRequestsFragment.ACCOUNT_ID));
             return;
+        } else if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            handleShareIntent(intent);
+            return;
         }
-        if (!DeviceUtils.isTablet(this) || !DRingService.ACTION_CONV_ACCEPT.equals(intent.getAction())) {
+        if (!DeviceUtils.isTablet(this) || !DRingService.ACTION_CONV_ACCEPT.equals(action)) {
             return;
         }
 
diff --git a/ring-android/app/src/main/java/cx/ring/client/ShareActivity.java b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.java
new file mode 100644
index 0000000..9c83479
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.java
@@ -0,0 +1,15 @@
+package cx.ring.client;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import cx.ring.R;
+
+public class ShareActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_share);
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java b/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
index d6e6270..e52c925 100644
--- a/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
+++ b/ring-android/app/src/main/java/cx/ring/contacts/AvatarFactory.java
@@ -103,11 +103,11 @@
         return getAvatar(context, contactPhoto, username, ringId, pictureSize);
     }
 
-    public static Drawable getAvatar(Context context, byte[] photo, String username, String ringId) {
+    public static BitmapDrawable getAvatar(Context context, byte[] photo, String username, String ringId) {
         return getAvatar(context, photo, username, ringId, Float.valueOf(context.getResources().getDisplayMetrics().density * DEFAULT_AVATAR_SIZE).intValue());
     }
 
-    public static Drawable getAvatar(Context context, byte[] photo, String username, String ringId, int pictureSize) {
+    public static BitmapDrawable getAvatar(Context context, byte[] photo, String username, String ringId, int pictureSize) {
         if (context == null || pictureSize <= 0) {
             throw new IllegalArgumentException();
         }
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
index d3c475e..fed0c12 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
@@ -44,6 +44,7 @@
 import cx.ring.fragments.MediaPreferenceFragment;
 import cx.ring.fragments.SIPAccountCreationFragment;
 import cx.ring.fragments.SecurityAccountFragment;
+import cx.ring.fragments.ShareWithFragment;
 import cx.ring.fragments.SmartListFragment;
 import cx.ring.launch.LaunchActivity;
 import cx.ring.navigation.RingNavigationFragment;
@@ -59,6 +60,7 @@
 import cx.ring.services.HistoryServiceImpl;
 import cx.ring.services.NotificationServiceImpl;
 import cx.ring.services.PresenceService;
+import cx.ring.services.RingChooserTargetService;
 import cx.ring.services.SharedPreferencesServiceImpl;
 import cx.ring.settings.SettingsFragment;
 import cx.ring.share.ShareFragment;
@@ -192,4 +194,8 @@
     void inject(TVSettingsFragment tvSettingsFragment);
 
     void inject(TVSettingsFragment.PrefsFragment prefsFragment);
+
+    void inject(RingChooserTargetService service);
+
+    void inject(ShareWithFragment fragment);
 }
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionModule.java
index 0b638ec..444a609 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionModule.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionModule.java
@@ -30,7 +30,7 @@
 @Module
 public class RingInjectionModule {
 
-    RingApplication mRingApplication;
+    private final RingApplication mRingApplication;
 
     public RingInjectionModule(RingApplication app) {
         mRingApplication = app;
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
index 5fe62f8..03d3707 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
@@ -21,6 +21,7 @@
 
 import android.Manifest;
 import android.app.DownloadManager;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
 import android.media.MediaScannerConnection;
@@ -676,4 +677,34 @@
                     true);
         }
     }
+
+    public void handleShareIntent(Intent intent) {
+        String type = intent.getType();
+        if (type == null) {
+            Log.w(TAG, "Can't share with no type");
+            return;
+        }
+        if (type.startsWith("text/")) {
+            mMsgEditTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
+        } else if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("application/")) {
+            android.net.Uri uri = intent.getData();
+            ClipData clip = intent.getClipData();
+            if (uri == null && clip != null && clip.getItemCount() > 0)
+                uri = clip.getItemAt(0).getUri();
+            if (uri == null)
+                return;
+            final android.net.Uri shareUri = uri;
+            setLoading(true);
+            new Thread(() -> {
+                try {
+                    File cacheFile = AndroidFileUtils.getCacheFile(getActivity(), shareUri);
+                    presenter.sendFile(cacheFile);
+                } catch (IOException e) {
+                    Log.e(TAG, "onActivityResult: not able to create cache file");
+                    getActivity().runOnUiThread(() -> displayErrorToast(RingError.INVALID_FILE));
+                }
+                getActivity().runOnUiThread(() -> setLoading(false));
+            }).start();
+        }
+    }
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java
new file mode 100644
index 0000000..d5b0b51
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java
@@ -0,0 +1,205 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.fragments;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.app.Fragment;
+
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import cx.ring.R;
+import cx.ring.adapters.SmartListAdapter;
+import cx.ring.application.RingApplication;
+import cx.ring.client.ConversationActivity;
+import cx.ring.facades.ConversationFacade;
+import cx.ring.model.Account;
+import cx.ring.services.ContactService;
+import cx.ring.smartlist.SmartListViewModel;
+import cx.ring.viewholders.SmartListViewHolder;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+
+public class ShareWithFragment extends Fragment {
+    private final static String TAG = ShareWithFragment.class.getSimpleName();
+
+    private CompositeDisposable mDisposable = new CompositeDisposable();
+
+    @Inject
+    @Singleton
+    ConversationFacade mConversationFacade;
+
+    @Inject
+    @Singleton
+    ContactService mContactService;
+
+    private Intent mPendingIntent = null;
+    private SmartListAdapter adapter;
+
+    private TextView previewText;
+    private ImageView previewImage;
+    private VideoView previewVideo;
+
+    /**
+     * Mandatory empty constructor for the fragment manager to instantiate the
+     * fragment (e.g. upon screen orientation changes).
+     */
+    public ShareWithFragment() {
+        RingApplication.getInstance().getRingInjectionComponent().inject(this);
+    }
+
+    public static ShareWithFragment newInstance() {
+        return new ShareWithFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.frag_sharewith, container, false);
+        RecyclerView list = view.findViewById(R.id.shareList);
+        Toolbar toolbar = view.findViewById(R.id.toolbar);
+        previewText = view.findViewById(R.id.previewText);
+        previewImage = view.findViewById(R.id.previewImage);
+        previewVideo = view.findViewById(R.id.previewVideo);
+
+        Context context = view.getContext();
+        Activity activity = getActivity();
+        if (activity instanceof AppCompatActivity) {
+            AppCompatActivity compatActivity = (AppCompatActivity) activity;
+            compatActivity.setSupportActionBar(toolbar);
+            ActionBar ab = compatActivity.getSupportActionBar();
+            if (ab != null)
+                ab.setDisplayHomeAsUpEnabled(true);
+        }
+
+        if (mPendingIntent != null) {
+            String type = mPendingIntent.getType();
+            ClipData clip = mPendingIntent.getClipData();
+            if (type.startsWith("text/")) {
+                previewText.setText(mPendingIntent.getStringExtra(Intent.EXTRA_TEXT));
+                previewText.setVisibility(View.VISIBLE);
+            } else if (type.startsWith("image/")) {
+                Uri data = mPendingIntent.getData();
+                if (data == null && clip != null && clip.getItemCount() > 0)
+                    data = clip.getItemAt(0).getUri();
+                previewImage.setImageURI(data);
+                previewImage.setVisibility(View.VISIBLE);
+            } else if (type.startsWith("video/")) {
+                Uri data = mPendingIntent.getData();
+                if (data == null && clip != null && clip.getItemCount() > 0)
+                    data = clip.getItemAt(0).getUri();
+                previewVideo.setVideoURI(data);
+                previewVideo.setVisibility(View.VISIBLE);
+                previewVideo.setOnCompletionListener(mediaPlayer -> previewVideo.start());
+            }
+        }
+
+        adapter = new SmartListAdapter(null, new SmartListViewHolder.SmartListListeners() {
+            @Override
+            public void onItemClick(SmartListViewModel smartListViewModel) {
+                if (mPendingIntent != null) {
+                    Intent intent = mPendingIntent;
+                    mPendingIntent = null;
+                    String type = intent.getType();
+                    if (type.startsWith("text/")) {
+                        intent.putExtra(Intent.EXTRA_TEXT, previewText.getText().toString());
+                    }
+                    intent.putExtra(ConversationFragment.KEY_ACCOUNT_ID, smartListViewModel.getAccountId());
+                    intent.putExtra(ConversationFragment.KEY_CONTACT_RING_ID, smartListViewModel.getContact().getPrimaryNumber());
+                    intent.setClass(getActivity(), ConversationActivity.class);
+                    startActivity(intent);
+                }
+            }
+
+            @Override
+            public void onItemLongClick(SmartListViewModel smartListViewModel) {
+
+            }
+        });
+        list.setLayoutManager(new LinearLayoutManager(context));
+        list.setAdapter(adapter);
+        return view;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mPendingIntent == null)
+            getActivity().finish();
+        mDisposable.add(mConversationFacade
+                .getCurrentAccountSubject()
+                .switchMap(Account::getConversationsViewModels)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(list -> {
+                    if (adapter != null)
+                        adapter.update(list);
+                }));
+        if (previewVideo != null && previewVideo.getVisibility() != View.GONE) {
+            previewVideo.start();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mDisposable.clear();
+    }
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Intent intent = getActivity().getIntent();
+        Bundle extra = intent.getExtras();
+        if (extra != null) {
+            String accountId = extra.getString(ConversationFragment.KEY_ACCOUNT_ID);
+            String uri = extra.getString(ConversationFragment.KEY_CONTACT_RING_ID);
+            if (!TextUtils.isEmpty(accountId) && !TextUtils.isEmpty(uri)) {
+                intent.setClass(getActivity(), ConversationActivity.class);
+                startActivity(intent);
+                return;
+            }
+        }
+        mPendingIntent = intent;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mPendingIntent = null;
+        adapter = null;
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java b/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java
new file mode 100644
index 0000000..4025198
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/RingChooserTargetService.java
@@ -0,0 +1,100 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.services;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.Bundle;
+import android.service.chooser.ChooserTarget;
+import android.service.chooser.ChooserTargetService;
+import android.support.annotation.RequiresApi;
+
+import com.bumptech.glide.Glide;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import cx.ring.application.RingApplication;
+import cx.ring.contacts.AvatarFactory;
+import cx.ring.facades.ConversationFacade;
+import cx.ring.fragments.ConversationFragment;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conversation;
+
+@RequiresApi(api = Build.VERSION_CODES.M)
+public class RingChooserTargetService extends ChooserTargetService {
+    @Inject
+    @Singleton
+    ConversationFacade conversationFacade;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        RingApplication.getInstance().startDaemon();
+        RingApplication.getInstance().getRingInjectionComponent().inject(this);
+    }
+
+    @Override
+    public List<ChooserTarget> onGetChooserTargets(ComponentName componentName, IntentFilter intentFilter) {
+        return conversationFacade
+                .getCurrentAccountSubject()
+                .firstOrError()
+                .flatMap(a -> a
+                        .getConversationsSubject()
+                        .firstOrError()
+                        .map(conversations -> {
+                            List<Future<Drawable>> futureIcons = new ArrayList<>(conversations.size());
+                            for (Conversation conversation : conversations) {
+                                CallContact contact = conversation.getContact();
+                                final BitmapDrawable contactPicture = AvatarFactory.getAvatar(
+                                        this,
+                                        contact.getPhoto(),
+                                        contact.getDisplayName(),
+                                        contact.getPrimaryNumber());
+                                futureIcons.add(Glide.with(this)
+                                        .load(contactPicture)
+                                        .apply(AvatarFactory.getGlideOptions(true, true))
+                                        .submit());
+                            }
+                            int i=0;
+                            List<ChooserTarget> choosers = new ArrayList<>(conversations.size());
+                            for (Conversation conversation : conversations) {
+                                CallContact contact = conversation.getContact();
+                                Bundle bundle = new Bundle();
+                                bundle.putString(ConversationFragment.KEY_ACCOUNT_ID, a.getAccountID());
+                                bundle.putString(ConversationFragment.KEY_CONTACT_RING_ID, contact.getPrimaryNumber());
+                                BitmapDrawable d = (BitmapDrawable) futureIcons.get(i).get();
+                                Icon icon = Icon.createWithBitmap(d.getBitmap());
+                                ChooserTarget target = new ChooserTarget(contact.getDisplayName(), icon, 1.f-(i/(float)conversations.size()), componentName, bundle);
+                                choosers.add(target);
+                                i++;
+                            }
+                            return choosers;
+                        }))
+                .blockingGet();
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/views/PreviewVideoView.java b/ring-android/app/src/main/java/cx/ring/views/PreviewVideoView.java
new file mode 100644
index 0000000..49c5844
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/PreviewVideoView.java
@@ -0,0 +1,74 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.views;
+
+import android.content.Context;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.widget.VideoView;
+
+public class PreviewVideoView extends VideoView {
+    private int mVideoWidth;
+    private int mVideoHeight;
+
+    public PreviewVideoView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public PreviewVideoView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public PreviewVideoView(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void setVideoURI(Uri uri) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(this.getContext(), uri);
+        int videoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+        int videoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+        int rotation = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+        if (rotation == 90 || rotation == 270) {
+            mVideoWidth = videoHeight;
+            mVideoHeight = videoWidth;
+        } else {
+            mVideoHeight = videoHeight;
+            mVideoWidth = videoWidth;
+        }
+        super.setVideoURI(uri);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
+        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
+        if (mVideoWidth > 0 && mVideoHeight > 0) {
+            if (mVideoWidth * height > width * mVideoHeight) {
+                height = width * mVideoHeight / mVideoWidth;
+            } else if (mVideoWidth * height < width * mVideoHeight) {
+                width = height * mVideoWidth / mVideoHeight;
+            }
+        }
+        setMeasuredDimension(width, height);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/activity_share.xml b/ring-android/app/src/main/res/layout/activity_share.xml
new file mode 100644
index 0000000..87467de
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/activity_share.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/shareFragment"
+    android:name="cx.ring.fragments.ShareWithFragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/ring-android/app/src/main/res/layout/frag_sharewith.xml b/ring-android/app/src/main/res/layout/frag_sharewith.xml
new file mode 100644
index 0000000..e41b063
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/frag_sharewith.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?attr/colorPrimary"
+        android:elevation="4dp"
+        android:minHeight="?attr/actionBarSize"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        app:title="Share with..." />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#888888"
+        android:elevation="2dp">
+
+        <EditText
+            android:id="@+id/previewText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@null"
+            android:maxHeight="@dimen/share_preview_height"
+            android:singleLine="false"
+            android:textColor="@color/text_color_primary_dark"
+            android:visibility="gone"
+            tools:text="Test text message" />
+
+        <ImageView
+            android:id="@+id/previewImage"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/share_preview_height"
+            android:layout_margin="8dp"
+            android:visibility="gone"
+            tools:visibility="visible" />
+
+        <cx.ring.views.PreviewVideoView
+            android:id="@+id/previewVideo"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/share_preview_height"
+            android:layout_gravity="center_horizontal"
+            android:layout_margin="8dp"
+            android:visibility="gone" />
+
+    </FrameLayout>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/shareList"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipToPadding="false"
+        android:paddingTop="8dp"
+        app:layoutManager="LinearLayoutManager"
+        tools:context=".fragments.ShareWithFragment"
+        tools:listitem="@layout/item_smartlist" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/dimens.xml b/ring-android/app/src/main/res/values/dimens.xml
index 19d3715..2179bf1 100644
--- a/ring-android/app/src/main/res/values/dimens.xml
+++ b/ring-android/app/src/main/res/values/dimens.xml
@@ -45,4 +45,6 @@
 
     <dimen name="search_image_card_width">100dp</dimen>
     <dimen name="search_image_card_height">90dp</dimen>
+
+    <dimen name="share_preview_height">120dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Account.java b/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
index 93ebc88..ecfa45c 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/Account.java
@@ -30,6 +30,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import cx.ring.smartlist.SmartListViewModel;
 import cx.ring.utils.Log;
 import cx.ring.utils.StringUtils;
 import io.reactivex.Observable;
@@ -62,83 +63,57 @@
     private final List<Conversation> sortedConversations = new ArrayList<>();
     private final List<Conversation> sortedPending = new ArrayList<>();
 
-    private final Subject<Conversation> conversationSubject = PublishSubject.create();
-    private final Subject<List<Conversation>> conversationsSubject = BehaviorSubject.create();
-    private final Subject<List<Conversation>> pendingSubject = BehaviorSubject.create();
-
     public boolean registeringUsername = false;
     private boolean conversationsChanged = true;
     private boolean pendingsChanged = true;
     private boolean historyLoaded = false;
 
+    private final Subject<Conversation> conversationSubject = PublishSubject.create();
+    private final Subject<List<Conversation>> conversationsSubject = BehaviorSubject.create();
+    private final Subject<List<Conversation>> pendingSubject = BehaviorSubject.create();
+
     private final BehaviorSubject<Collection<CallContact>> contactListSubject = BehaviorSubject.create();
     private final BehaviorSubject<Collection<TrustRequest>> trustRequestsSubject = BehaviorSubject.create();
-    private final Subject<RequestEvent> trustRequestSubject = PublishSubject.create();
+    public Subject<Account> historyLoader;
 
+    public Account(String bAccountID) {
+        accountID = bAccountID;
+        mDetails = new AccountConfig();
+        mVolatileDetails = new AccountConfig();
+    }
+    public Account(String bAccountID, final Map<String, String> details,
+                   final List<Map<String, String>> credentials,
+                   final Map<String, String> volDetails) {
+        accountID = bAccountID;
+        mDetails = new AccountConfig(details);
+        mVolatileDetails = new AccountConfig(volDetails);
+        setCredentials(credentials);
+    }
 
     public Observable<List<Conversation>> getConversationsSubject() {
         return conversationsSubject;
     }
+    public Observable<List<SmartListViewModel>> getConversationsViewModels() {
+        return conversationsSubject
+                .map(conversations -> {
+                    ArrayList<SmartListViewModel> viewModel = new ArrayList<>(conversations.size());
+                    for (Conversation c : conversations)
+                        viewModel.add(new SmartListViewModel(accountID, c.getContact(), c.getLastEvent()));
+                    return viewModel;
+                });
+    }
+
     public Observable<Conversation> getConversationSubject() {
         return conversationSubject;
     }
+    public Observable<SmartListViewModel> getConversationViewModel() {
+        return conversationSubject.map(c -> new SmartListViewModel(accountID, c.getContact(), c.getLastEvent()));
+    }
+
     public Observable<List<Conversation>> getPendingSubject() {
         return pendingSubject;
     }
 
-    public Subject<Account> historyLoader;
-
-    private static class ConversationComparator implements Comparator<Conversation> {
-        @Override
-        public int compare(Conversation a, Conversation b) {
-            ConversationElement eventA = a.getLastEvent();
-            ConversationElement eventB = b.getLastEvent();
-            if (eventA == null) {
-                if (eventB == null)
-                    return 0;
-                return 1;
-            }
-            if (eventB == null)
-                return -1;
-            return Long.compare(eventB.getDate(), eventA.getDate());
-        }
-    }
-
-    private static final ConversationComparator CONVERSATION_COMPARATOR = new ConversationComparator();
-
-    private List<Conversation> getSortedConversations() {
-        Log.w(TAG, "getSortedConversations() " + Thread.currentThread().getId());
-        synchronized (sortedConversations) {
-            if (conversationsChanged) {
-                sortedConversations.clear();
-                synchronized (conversations) {
-                    sortedConversations.addAll(conversations.values());
-                }
-                for (Conversation c : sortedConversations)
-                    c.sortHistory();
-                Collections.sort(sortedConversations, CONVERSATION_COMPARATOR);
-                conversationsChanged = false;
-            }
-        }
-        return sortedConversations;
-    }
-
-    private List<Conversation> getSortedPending() {
-        synchronized (sortedPending) {
-            if (pendingsChanged) {
-                sortedPending.clear();
-                synchronized (pending) {
-                    sortedPending.addAll(pending.values());
-                }
-                for (Conversation c : sortedPending)
-                    c.sortHistory();
-                Collections.sort(sortedPending, CONVERSATION_COMPARATOR);
-                pendingsChanged = false;
-            }
-        }
-        return sortedPending;
-    }
-
     public Collection<Conversation> getConversations() {
         return conversations.values();
     }
@@ -147,11 +122,11 @@
         return pending.values();
     }
 
-    public void pendingRefreshed() {
+    private void pendingRefreshed() {
         if (historyLoaded)
             pendingSubject.onNext(getSortedPending());
     }
-    public void pendingChanged() {
+    private void pendingChanged() {
         pendingsChanged = true;
         pendingRefreshed();
     }
@@ -168,11 +143,11 @@
         pendingSubject.onNext(getSortedPending());
     }
 
-    public void conversationRefreshed(Conversation conversation) {
+    private void conversationRefreshed(Conversation conversation) {
         if (historyLoaded)
             conversationSubject.onNext(conversation);
     }
-    public void conversationChanged() {
+    private void conversationChanged() {
         conversationsChanged = true;
         if (historyLoaded)
             conversationsSubject.onNext(getSortedConversations());
@@ -241,43 +216,6 @@
         return conversation;
     }
 
-    public static class ContactEvent {
-        public final CallContact contact;
-        public final boolean added;
-        ContactEvent(CallContact c, boolean a) { contact=c; added=a;}
-    }
-    public static class RequestEvent {
-        public final TrustRequest request;
-        public final boolean added;
-        RequestEvent(TrustRequest c, boolean a) { request=c; added=a;}
-    }
-
-    private final Observable<CallContact> contactsObservable = Observable.create(subscriber -> {
-        for (CallContact c : mContacts.values())
-            subscriber.onNext(c);
-        subscriber.onComplete();
-    });
-    private final Observable<CallContact> validContactsObservable = contactsObservable.filter(c -> !c.isBanned());
-    private final Observable<CallContact> bannedContactsObservable = contactsObservable.filter(CallContact::isBanned);
-
-    public Observable<CallContact> getValidContacts() {
-        return validContactsObservable;
-    }
-
-    public Observable<Collection<CallContact>> getContactsUpdates() {
-        return contactListSubject;
-    }
-    public Observable<Collection<TrustRequest>> getRequestsUpdates() {
-        return trustRequestsSubject;
-    }
-    public Observable<RequestEvent> getRequestsEvents() {
-        return trustRequestSubject;
-    }
-    public Observable<Collection<CallContact>> getValidContactsUpdates() {
-        return contactListSubject
-                .concatMapSingle(list -> Observable.fromIterable(list)
-                        .filter(c -> !c.isBanned()).toList());
-    }
     public Observable<Collection<CallContact>> getBannedContactsUpdates() {
         return contactListSubject.concatMapSingle(list -> Observable.fromIterable(list).filter(CallContact::isBanned).toList());
     }
@@ -294,27 +232,11 @@
         return getContactFromCache(uri.getRawUriString());
     }
 
-    public Account(String bAccountID) {
-        accountID = bAccountID;
-        mDetails = new AccountConfig();
-        mVolatileDetails = new AccountConfig();
-    }
-
     public void dispose() {
         contactListSubject.onComplete();
-        //contactSubject.onComplete();
         trustRequestsSubject.onComplete();
     }
 
-    public Account(String bAccountID, final Map<String, String> details,
-                   final List<Map<String, String>> credentials,
-                   final Map<String, String> volDetails) {
-        accountID = bAccountID;
-        mDetails = new AccountConfig(details);
-        mVolatileDetails = new AccountConfig(volDetails);
-        setCredentials(credentials);
-    }
-
     public Map<String, String> getDevices() {
         return devices;
     }
@@ -431,7 +353,7 @@
         mDetails.put(ConfigKey.ACCOUNT_ALIAS, alias);
     }
 
-    public String getDetail(ConfigKey key) {
+    private String getDetail(ConfigKey key) {
         return mDetails.get(key);
     }
 
@@ -510,7 +432,7 @@
         return mDetails.getBool(ConfigKey.SRTP_ENABLE) || mDetails.getBool(ConfigKey.TLS_ENABLE);
     }
 
-    public String getUri(boolean display) {
+    private String getUri(boolean display) {
         String username = display ? getDisplayUsername() : getUsername();
         if (isRing()) {
             return username;
@@ -651,10 +573,10 @@
         synchronized (pending) {
             mRequests.put(request.getContactId(), request);
             CallContact contact = getContactFromCache(request.getContactId());
-            if (contact.vcard == null) {
-                contact.vcard = request.getVCard();
+            if (!contact.detailsLoaded) {
+                contact.setVCardProfile(request.getVCard());
             }
-            trustRequestSubject.onNext(new RequestEvent(request, true));
+            //trustRequestSubject.onNext(new RequestEvent(request, true));
             trustRequestsSubject.onNext(mRequests.values());
 
             String key = new Uri(request.getContactId()).getRawUriString();
@@ -680,7 +602,7 @@
                     pending.put(key, conversation);
                     conversation.addRequestEvent(request);
                 }
-                trustRequestSubject.onNext(new RequestEvent(request, true));
+                //trustRequestSubject.onNext(new RequestEvent(request, true));
             }
             trustRequestsSubject.onNext(mRequests.values());
             pendingChanged();
@@ -691,7 +613,7 @@
         synchronized (pending) {
             TrustRequest request = mRequests.remove(contact.getRawUriString());
             if (request != null) {
-                trustRequestSubject.onNext(new RequestEvent(request, true));
+                //trustRequestSubject.onNext(new RequestEvent(request, true));
                 trustRequestsSubject.onNext(mRequests.values());
             }
             if (pending.remove(contact.getRawUriString()) != null) {
@@ -730,7 +652,59 @@
         return null;
     }
 
-    public Conversation getByKey(String key) {
+    public boolean isHistoryLoaded() {
+        return historyLoaded;
+    }
+
+    public void setHistoryLoaded(Set<Conversation> conversations) {
+        if (historyLoaded)
+            return;
+        Log.w(TAG, "setHistoryLoaded() " + conversations.size());
+        for (Conversation c : conversations)
+            updated(c);
+        historyLoaded = true;
+        if (historyLoader != null) {
+            historyLoader.onNext(this);
+            historyLoader.onComplete();
+        }
+        conversationChanged();
+        pendingChanged();
+    }
+
+    private List<Conversation> getSortedConversations() {
+        Log.w(TAG, "getSortedConversations() " + Thread.currentThread().getId());
+        synchronized (sortedConversations) {
+            if (conversationsChanged) {
+                sortedConversations.clear();
+                synchronized (conversations) {
+                    sortedConversations.addAll(conversations.values());
+                }
+                for (Conversation c : sortedConversations)
+                    c.sortHistory();
+                Collections.sort(sortedConversations, new ConversationComparator());
+                conversationsChanged = false;
+            }
+        }
+        return sortedConversations;
+    }
+
+    private List<Conversation> getSortedPending() {
+        synchronized (sortedPending) {
+            if (pendingsChanged) {
+                sortedPending.clear();
+                synchronized (pending) {
+                    sortedPending.addAll(pending.values());
+                }
+                for (Conversation c : sortedPending)
+                    c.sortHistory();
+                Collections.sort(sortedPending, new ConversationComparator());
+                pendingsChanged = false;
+            }
+        }
+        return sortedPending;
+    }
+
+    private Conversation getByKey(String key) {
         Conversation conversation = cache.get(key);
         if (conversation != null) {
             return conversation;
@@ -741,7 +715,7 @@
         return conversation;
     }
 
-    public void contactAdded(CallContact contact) {
+    private void contactAdded(CallContact contact) {
         Uri uri = contact.getPrimaryUri();
         String key = uri.getRawUriString();
         synchronized (conversations) {
@@ -763,7 +737,7 @@
         }
     }
 
-    public void contactRemoved(Uri uri) {
+    private void contactRemoved(Uri uri) {
         String key = uri.getRawUriString();
         synchronized (conversations) {
             synchronized (pending) {
@@ -775,26 +749,7 @@
         }
     }
 
-    public boolean isHistoryLoaded() {
-        return historyLoaded;
-    }
-
-    public void setHistoryLoaded(Set<Conversation> conversations) {
-        if (historyLoaded)
-            return;
-        Log.w(TAG, "setHistoryLoaded() " + conversations.size());
-        for (Conversation c : conversations)
-            updated(c);
-        historyLoaded = true;
-        if (historyLoader != null) {
-            historyLoader.onNext(this);
-            historyLoader.onComplete();
-        }
-        conversationChanged();
-        pendingChanged();
-    }
-
-    public Conversation getConversationByCallId(String callId) {
+    private Conversation getConversationByCallId(String callId) {
         for (Conversation conversation : conversations.values()) {
             Conference conf = conversation.getConference(callId);
             if (conf != null) {
@@ -804,4 +759,20 @@
         return null;
     }
 
+    private static class ConversationComparator implements Comparator<Conversation> {
+        @Override
+        public int compare(Conversation a, Conversation b) {
+            ConversationElement eventA = a.getLastEvent();
+            ConversationElement eventB = b.getLastEvent();
+            if (eventA == null) {
+                if (eventB == null)
+                    return 0;
+                return 1;
+            }
+            if (eventB == null)
+                return -1;
+            return Long.compare(eventB.getDate(), eventA.getDate());
+        }
+    }
+
 }
diff --git a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java
index 871cdc2..ab857ef 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java
@@ -42,6 +42,17 @@
         hasUnreadTextMessage = (lastEvent != null) && !lastEvent.isRead();
         this.hasOngoingCall = false;
         this.lastEvent = lastEvent;
+        isOnline = contact.isOnline();
+    }
+    public SmartListViewModel(String accountId, CallContact contact, ConversationElement lastEvent) {
+        this.accountId = accountId;
+        this.contact = contact;
+        this.uuid = contact.getIds().get(0);
+        this.contactName = contact.getDisplayName();
+        hasUnreadTextMessage = (lastEvent != null) && !lastEvent.isRead();
+        this.hasOngoingCall = false;
+        this.lastEvent = lastEvent;
+        isOnline = contact.isOnline();
     }
 
     public CallContact getContact() {