smartlist: add progress indicator

- Rename to SmartList to match all clients convention.
- Separate SmartListAdapter from its fragment.

Change-Id: Ia696d351092419f6132a19816d775c18a95b2007
Tuleap: #468
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
new file mode 100644
index 0000000..5f35ed4
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
@@ -0,0 +1,179 @@
+/*
+ *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *           Romain Bertozzi <romain.bertozzi@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.adapters;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.concurrent.ExecutorService;
+
+import cx.ring.R;
+import cx.ring.model.Conversation;
+
+public class SmartListAdapter extends BaseAdapter {
+    private static String TAG = SmartListAdapter.class.getSimpleName();
+
+    final private ArrayList<Conversation> mCalls = new ArrayList<>();
+    final private ExecutorService mInfosFetcher;
+    final private LruCache<Long, Bitmap> mMemoryCache;
+    final private HashMap<Long, WeakReference<ContactPictureTask>> mRunningTasks = new HashMap<>();
+
+    final private Context mContext;
+
+    public SmartListAdapter(Context act, LruCache<Long, Bitmap> cache, ExecutorService pool) {
+        super();
+        mContext = act;
+        mMemoryCache = cache;
+        mInfosFetcher = pool;
+    }
+
+    public void updateDataset(final Collection<Conversation> list) {
+        Log.i(TAG, "updateDataset " + list.size());
+
+        if (list.size() == 0 && mCalls.size() == 0) {
+            return;
+        }
+
+        mCalls.clear();
+        for (Conversation c : list) {
+            if (!c.getContact().isUnknown() || !c.getAccountsUsed().isEmpty() || c.getCurrentCall() != null)
+                mCalls.add(c);
+        }
+
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getCount() {
+        return mCalls.size();
+    }
+
+    @Override
+    public Conversation getItem(int position) {
+        return mCalls.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    public class ViewHolder {
+        TextView conv_participants;
+        TextView conv_status;
+        TextView conv_time;
+        ImageView photo;
+        int position;
+        public Conversation conv;
+    }
+
+    @Override
+    public View getView(final int position, View convertView, ViewGroup parent) {
+        if (convertView == null)
+            convertView = LayoutInflater.from(mContext).inflate(cx.ring.R.layout.item_calllist, null);
+
+        ViewHolder holder = (ViewHolder) convertView.getTag();
+        if (holder == null) {
+            holder = new ViewHolder();
+            holder.photo = (ImageView) convertView.findViewById(R.id.photo);
+            holder.conv_participants = (TextView) convertView.findViewById(R.id.conv_participant);
+            holder.conv_status = (TextView) convertView.findViewById(R.id.conv_last_item);
+            holder.conv_time = (TextView) convertView.findViewById(R.id.conv_last_time);
+            holder.position = -1;
+            convertView.setTag(holder);
+        }
+        final ViewHolder h = holder;
+        h.conv = mCalls.get(position);
+        h.position = position;
+        h.conv_participants.setText(h.conv.getContact().getDisplayName());
+        long last_interaction = h.conv.getLastInteraction().getTime();
+        h.conv_time.setText(last_interaction == 0 ? "" : DateUtils.getRelativeTimeSpanString(last_interaction, System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL));
+        h.conv_status.setText(h.conv.getLastInteractionSumary(mContext.getResources()));
+        if (h.conv.hasUnreadTextMessages()) {
+            h.conv_participants.setTypeface(null, Typeface.BOLD);
+            h.conv_time.setTypeface(null, Typeface.BOLD);
+            h.conv_status.setTypeface(null, Typeface.BOLD);
+        } else {
+            h.conv_participants.setTypeface(null, Typeface.NORMAL);
+            h.conv_time.setTypeface(null, Typeface.NORMAL);
+            h.conv_status.setTypeface(null, Typeface.NORMAL);
+        }
+
+        final Long cid = h.conv.getContact().getId();
+        Bitmap bmp = mMemoryCache.get(cid);
+        if (bmp != null) {
+            h.photo.setImageBitmap(bmp);
+        } else {
+            holder.photo.setImageBitmap(mMemoryCache.get(-1l));
+            final WeakReference<ViewHolder> wh = new WeakReference<>(holder);
+            final ContactPictureTask.PictureLoadedCallback cb = new ContactPictureTask.PictureLoadedCallback() {
+                @Override
+                public void onPictureLoaded(final Bitmap bmp) {
+                    final ViewHolder fh = wh.get();
+                    if (fh == null || fh.photo.getParent() == null)
+                        return;
+                    if (fh.conv.getContact().getId() == cid) {
+                        fh.photo.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                fh.photo.setImageBitmap(bmp);
+                                fh.photo.startAnimation(AnimationUtils.loadAnimation(fh.photo.getContext(), R.anim.contact_fadein));
+                            }
+                        });
+                    }
+                }
+            };
+            WeakReference<ContactPictureTask> wtask = mRunningTasks.get(cid);
+            ContactPictureTask task = wtask == null ? null : wtask.get();
+            if (task != null) {
+                task.addCallback(cb);
+            } else {
+                task = new ContactPictureTask(mContext, h.photo, h.conv.getContact(), new ContactPictureTask.PictureLoadedCallback() {
+                    @Override
+                    public void onPictureLoaded(Bitmap bmp) {
+                        mMemoryCache.put(cid, bmp);
+                        mRunningTasks.remove(cid);
+                    }
+                });
+                task.addCallback(cb);
+                mRunningTasks.put(cid, new WeakReference<>(task));
+                mInfosFetcher.execute(task);
+            }
+        }
+        return convertView;
+    }
+}
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 7260d92..c0f45b4 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
@@ -31,7 +31,7 @@
 import cx.ring.R;
 import cx.ring.fragments.AboutFragment;
 import cx.ring.fragments.AccountsManagementFragment;
-import cx.ring.fragments.CallListFragment;
+import cx.ring.fragments.SmartListFragment;
 import cx.ring.fragments.ContactListFragment;
 import cx.ring.views.MenuHeaderView;
 import cx.ring.fragments.SettingsFragment;
@@ -426,7 +426,7 @@
             FragmentManager fm = getFragmentManager();
             fContent = fm.findFragmentById(R.id.main_frame);
             if (fContent == null) {
-                fContent = new CallListFragment();
+                fContent = new SmartListFragment();
                 fm.beginTransaction().replace(R.id.main_frame, fContent, "Home").addToBackStack("Home").commit();
             } else if (fContent instanceof Refreshable) {
                 fm.beginTransaction().replace(R.id.main_frame, fContent).addToBackStack("Home").commit();
@@ -529,7 +529,7 @@
         switch (pos.getItemId()) {
             case R.id.menuitem_home:
 
-                if (fContent instanceof CallListFragment)
+                if (fContent instanceof SmartListFragment)
                     break;
 
                 if (getFragmentManager().getBackStackEntryCount() == 1)
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
similarity index 73%
rename from ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java
rename to ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
index 1da4b16..a171714 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
@@ -28,35 +28,37 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.Loader;
-import android.graphics.Bitmap;
-import android.graphics.Typeface;
 import android.net.Uri;
-import android.os.*;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.support.design.widget.FloatingActionButton;
 import android.support.v4.view.MenuItemCompat;
 import android.support.v7.widget.SearchView;
 import android.support.v7.widget.Toolbar;
-import android.text.format.DateUtils;
 import android.util.Log;
-import android.util.LruCache;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
-import android.widget.*;
+import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
 
 import com.google.zxing.integration.android.IntentIntegrator;
 import com.google.zxing.integration.android.IntentResult;
 
 import cx.ring.R;
-import cx.ring.adapters.ContactPictureTask;
 import cx.ring.adapters.ContactsAdapter;
+import cx.ring.adapters.SmartListAdapter;
 import cx.ring.adapters.StarredContactsAdapter;
 import cx.ring.client.ConversationActivity;
 import cx.ring.client.HomeActivity;
@@ -64,22 +66,17 @@
 import cx.ring.loaders.LoaderConstants;
 import cx.ring.model.CallContact;
 import cx.ring.model.Conference;
-import cx.ring.model.Conversation;
 import cx.ring.service.LocalService;
 import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.concurrent.ExecutorService;
-
-public class CallListFragment extends Fragment implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<ContactsLoader.Result>, HomeActivity.Refreshable {
-
-    private static final String TAG = CallListFragment.class.getSimpleName();
+public class SmartListFragment extends Fragment implements SearchView.OnQueryTextListener,
+        LoaderManager.LoaderCallbacks<ContactsLoader.Result>,
+        HomeActivity.Refreshable
+{
+    private static final String TAG = SmartListFragment.class.getSimpleName();
 
     private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS;
-    private CallListAdapter mConferenceAdapter;
+    private SmartListAdapter mSmartListAdapter;
     private ContactsAdapter mListAdapter;
     private StarredContactsAdapter mGridAdapter;
 
@@ -91,6 +88,8 @@
 
     private ListView list = null;
     private StickyListHeadersListView contactList = null;
+    private View mLoader = null;
+    private TextView mEmptyTextView = null;
 
     private LinearLayout llMain;
     private GridView mStarredGrid;
@@ -106,6 +105,7 @@
         super.onStart();
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(LocalService.ACTION_CONF_UPDATE);
+        intentFilter.addAction(LocalService.ACTION_CONF_LOADED);
         intentFilter.addAction(LocalService.ACTION_ACCOUNT_UPDATE);
         getActivity().registerReceiver(receiver, intentFilter);
     }
@@ -121,6 +121,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             Log.w(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString());
+            if (LocalService.ACTION_CONF_LOADED.equals(intent.getAction())) {
+                setLoading(false);
+            }
             refresh();
         }
     };
@@ -146,9 +149,14 @@
             Log.e(TAG, "refresh: null service");
             return;
         }
-        if (mConferenceAdapter == null)
+
+        if (mSmartListAdapter == null) {
             bindService(getActivity(), service);
-        mConferenceAdapter.updateDataset(service.getConversations());
+        }
+        else {
+            mSmartListAdapter.updateDataset(service.getConversations());
+        }
+
         if (service.isConnected()) {
             error_msg_pane.setVisibility(View.GONE);
         } else {
@@ -174,7 +182,6 @@
     public void onPause() {
         Log.i(TAG, "onPause");
         super.onPause();
-        //mHandler.removeCallbacks(mUpdateTimeTask);
     }
 
     @Override
@@ -200,24 +207,24 @@
             @Override
             public boolean onMenuItemActionCollapse(MenuItem item) {
                 dialpadMenuItem.setVisible(false);
-                list.setAdapter(mConferenceAdapter);
-                //listSwitcher.setDisplayedChild(0);
+                list.setAdapter(mSmartListAdapter);
                 list.setVisibility(View.VISIBLE);
                 contactList.setAdapter(null);
                 contactList.setVisibility(View.GONE);
                 newconv_btn.setVisibility(View.VISIBLE);
+                setLoading(false);
                 return true;
             }
             @Override
             public boolean onMenuItemActionExpand(MenuItem item) {
                 dialpadMenuItem.setVisible(true);
                 contactList.setAdapter(mListAdapter);
-                //listSwitcher.setDisplayedChild(1);
                 contactList.setVisibility(View.VISIBLE);
                 list.setAdapter(null);
                 list.setVisibility(View.GONE);
                 newconv_btn.setVisibility(View.GONE);
                 onLoadFinished(null, mCallbacks.getService().getSortedContacts());
+                setLoading(false);
                 return true;
             }
         });
@@ -302,10 +309,12 @@
             }
         });
 
-
         list = (ListView) inflatedView.findViewById(cx.ring.R.id.confs_list);
         list.setOnItemClickListener(callClickListener);
-        //list.setOnItemLongClickListener(mItemLongClickListener);
+
+        this.mEmptyTextView = (TextView) inflatedView.findViewById(R.id.emptyTextView);
+        this.mLoader = inflatedView.findViewById(android.R.id.empty);
+        this.setLoading(true);
 
         mHeader = (LinearLayout) inflater.inflate(R.layout.frag_contact_list_header, null);
         contactList = (StickyListHeadersListView) inflatedView.findViewById(R.id.contacts_stickylv);
@@ -349,19 +358,29 @@
         contactList.setVisibility(View.GONE);
 
         LocalService service = mCallbacks.getService();
-        if (service != null)
+        if (service != null) {
             bindService(inflater.getContext(), service);
+            if (service.areConversationsLoaded()) {
+                setLoading(false);
+            }
+        }
 
         return inflatedView;
     }
 
     public void bindService(final Context ctx, final LocalService service) {
-        mConferenceAdapter = new CallListAdapter(ctx, service.get40dpContactCache(), service.getThreadPool());
-        mListAdapter = new ContactsAdapter(ctx, (HomeActivity)getActivity(), service.get40dpContactCache(), service.getThreadPool());
+        mSmartListAdapter = new SmartListAdapter(ctx,
+                service.get40dpContactCache(),
+                service.getThreadPool());
+        mListAdapter = new ContactsAdapter(ctx,
+                (HomeActivity)getActivity(),
+                service.get40dpContactCache(),
+                service.getThreadPool());
         mGridAdapter = new StarredContactsAdapter(ctx);
 
-        mConferenceAdapter.updateDataset(service.getConversations());
-        list.setAdapter(mConferenceAdapter);
+        mSmartListAdapter.updateDataset(service.getConversations());
+
+        list.setAdapter(mSmartListAdapter);
     }
 
     private void startConversation(CallContact c) {
@@ -369,15 +388,13 @@
                 .setClass(getActivity(), ConversationActivity.class)
                 .setAction(Intent.ACTION_VIEW)
                 .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, c.getIds().get(0)));
-        //intent.putExtra("resuming", true);
         startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION);
     }
 
     private final OnItemClickListener callClickListener = new OnItemClickListener() {
-
         @Override
         public void onItemClick(AdapterView<?> arg0, View v, int arg2, long arg3) {
-            startConversation(((CallListAdapter.ViewHolder) v.getTag()).conv.getContact());
+            startConversation(((SmartListAdapter.ViewHolder) v.getTag()).conv.getContact());
         }
     };
 
@@ -410,7 +427,6 @@
         Log.i(TAG, "onLoadFinished with " + data.contacts.size() + " contacts, " + data.starred.size() + " starred.");
 
         mListAdapter.setData(data.contacts);
-        //setListViewListeners();
 
         mGridAdapter.setData(data.starred);
         if (data.starred.isEmpty()) {
@@ -459,135 +475,6 @@
 
     @Override
     public void onLoaderReset(Loader<ContactsLoader.Result> loader) {
-
-    }
-
-    public class CallListAdapter extends BaseAdapter {
-        final private ArrayList<Conversation> calls = new ArrayList<>();
-        final private ExecutorService infos_fetcher;
-        final private LruCache<Long, Bitmap> mMemoryCache;
-        final private HashMap<Long, WeakReference<ContactPictureTask>> running_tasks = new HashMap<>();
-
-        final private Context mContext;
-
-        public CallListAdapter(Context act, LruCache<Long, Bitmap> cache, ExecutorService pool) {
-            super();
-            mContext = act;
-            mMemoryCache = cache;
-            infos_fetcher = pool;
-        }
-
-        public void updateDataset(final Collection<Conversation> list) {
-            Log.i(TAG, "updateDataset " + list.size());
-            if (list.size() == 0 && calls.size() == 0)
-                return;
-            calls.clear();
-            for (Conversation c : list) {
-                if (!c.getContact().isUnknown() || !c.getAccountsUsed().isEmpty() || c.getCurrentCall() != null)
-                    calls.add(c);
-            }
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public int getCount() {
-            return calls.size();
-        }
-
-        @Override
-        public Conversation getItem(int position) {
-            return calls.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return 0;
-        }
-
-        private class ViewHolder {
-            TextView conv_participants;
-            TextView conv_status;
-            TextView conv_time;
-            ImageView photo;
-            int position;
-            Conversation conv;
-        }
-
-        @Override
-        public View getView(final int position, View convertView, ViewGroup parent) {
-            if (convertView == null)
-                convertView = LayoutInflater.from(mContext).inflate(cx.ring.R.layout.item_calllist, null);
-
-            ViewHolder holder = (ViewHolder) convertView.getTag();
-            if (holder == null) {
-                holder = new ViewHolder();
-                holder.photo = (ImageView) convertView.findViewById(R.id.photo);
-                holder.conv_participants = (TextView) convertView.findViewById(R.id.conv_participant);
-                holder.conv_status = (TextView) convertView.findViewById(R.id.conv_last_item);
-                holder.conv_time = (TextView) convertView.findViewById(R.id.conv_last_time);
-                holder.position = -1;
-                convertView.setTag(holder);
-            }
-            final ViewHolder h = holder;
-            h.conv = calls.get(position);
-            h.position = position;
-            h.conv_participants.setText(h.conv.getContact().getDisplayName());
-            long last_interaction = h.conv.getLastInteraction().getTime();
-            h.conv_time.setText(last_interaction == 0 ? "" : DateUtils.getRelativeTimeSpanString(last_interaction, System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL));
-            h.conv_status.setText(h.conv.getLastInteractionSumary(getResources()));
-            if (h.conv.hasUnreadTextMessages()) {
-                h.conv_participants.setTypeface(null, Typeface.BOLD);
-                h.conv_time.setTypeface(null, Typeface.BOLD);
-                h.conv_status.setTypeface(null, Typeface.BOLD);
-            } else {
-                h.conv_participants.setTypeface(null, Typeface.NORMAL);
-                h.conv_time.setTypeface(null, Typeface.NORMAL);
-                h.conv_status.setTypeface(null, Typeface.NORMAL);
-            }
-
-            final Long cid = h.conv.getContact().getId();
-            Bitmap bmp = mMemoryCache.get(cid);
-            if (bmp != null) {
-                h.photo.setImageBitmap(bmp);
-            } else {
-                holder.photo.setImageBitmap(mMemoryCache.get(-1l));
-                final WeakReference<ViewHolder> wh = new WeakReference<>(holder);
-                final ContactPictureTask.PictureLoadedCallback cb = new ContactPictureTask.PictureLoadedCallback() {
-                    @Override
-                    public void onPictureLoaded(final Bitmap bmp) {
-                        final ViewHolder fh = wh.get();
-                        if (fh == null || fh.photo.getParent() == null)
-                            return;
-                        if (fh.conv.getContact().getId() == cid) {
-                            fh.photo.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    fh.photo.setImageBitmap(bmp);
-                                    fh.photo.startAnimation(AnimationUtils.loadAnimation(fh.photo.getContext(), R.anim.contact_fadein));
-                                }
-                            });
-                        }
-                    }
-                };
-                WeakReference<ContactPictureTask> wtask = running_tasks.get(cid);
-                ContactPictureTask task = wtask == null ? null : wtask.get();
-                if (task != null) {
-                    task.addCallback(cb);
-                } else {
-                    task = new ContactPictureTask(mContext, h.photo, h.conv.getContact(), new ContactPictureTask.PictureLoadedCallback() {
-                        @Override
-                        public void onPictureLoaded(Bitmap bmp) {
-                            mMemoryCache.put(cid, bmp);
-                            running_tasks.remove(cid);
-                        }
-                    });
-                    task.addCallback(cb);
-                    running_tasks.put(cid, new WeakReference<>(task));
-                    infos_fetcher.execute(task);
-                }
-            }
-            return convertView;
-        }
     }
 
     @Override
@@ -601,7 +488,7 @@
                     transfer = data.getParcelableExtra("transfer");
                     try {
                         mCallbacks.getService().getRemoteService().attendedTransfer(transfer.getParticipants().get(0).getCallId(), c.getParticipants().get(0).getCallId());
-                        mConferenceAdapter.notifyDataSetChanged();
+                        mSmartListAdapter.notifyDataSetChanged();
                     } catch (RemoteException e) {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
@@ -678,4 +565,30 @@
         }
     }
 
+    private void setLoading(boolean loading) {
+        if (null != this.mLoader) {
+            int loaderVisibility = (loading) ? View.VISIBLE : View.GONE;
+            this.mLoader.setVisibility(loaderVisibility);
+            this.initEmptyTextViewWhileLoading(loading);
+        }
+    }
+
+    private void initEmptyTextViewWhileLoading(boolean loading) {
+        if (null != this.contactList && this.contactList.getVisibility() == View.VISIBLE) {
+            this.mEmptyTextView.setText("");
+        }
+        else  {
+            if (loading) {
+                this.mEmptyTextView.setText("");
+            }
+            else {
+                String emptyText = getResources().getQuantityString(R.plurals.home_conferences_title, 0, 0);
+                this.mEmptyTextView.setText(emptyText);
+            }
+        }
+
+        if (null != list) {
+            list.setEmptyView(this.mEmptyTextView);
+        }
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
index d60e86b..336c7a7 100644
--- a/ring-android/app/src/main/java/cx/ring/service/LocalService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java
@@ -102,6 +102,7 @@
 
     // Emitting events
     static public final String ACTION_CONF_UPDATE = BuildConfig.APPLICATION_ID + ".action.CONF_UPDATE";
+    static public final String ACTION_CONF_LOADED = BuildConfig.APPLICATION_ID + ".action.CONF_LOADED";
     static public final String ACTION_ACCOUNT_UPDATE = BuildConfig.APPLICATION_ID + ".action.ACCOUNT_UPDATE";
     static public final String ACTION_CONV_READ = BuildConfig.APPLICATION_ID + ".action.CONV_READ";
 
@@ -148,6 +149,8 @@
     private boolean canUseContacts = true;
     private boolean canUseMobile = false;
 
+    private boolean mAreConversationsLoaded = false;
+
     public ContactsLoader.Result getSortedContacts() {
         Log.w(TAG, "getSortedContacts " + lastContactLoaderResult.contacts.size() + " contacts, " + lastContactLoaderResult.starred.size() + " starred.");
         return lastContactLoaderResult;
@@ -1025,6 +1028,8 @@
         updateAudioState();
         updateTextNotifications();
         sendBroadcast(new Intent(ACTION_CONF_UPDATE));
+        sendBroadcast(new Intent(ACTION_CONF_LOADED));
+        this.mAreConversationsLoaded = true;
     }
 
     public class AccountsLoader extends AsyncTaskLoader<ArrayList<Account>> {
@@ -1447,4 +1452,7 @@
         getContentResolver().unregisterContentObserver(contactContentObserver);
     }
 
+    public boolean areConversationsLoaded() {
+        return mAreConversationsLoaded;
+    }
 }
diff --git a/ring-android/app/src/main/res/layout/frag_call_list.xml b/ring-android/app/src/main/res/layout/frag_call_list.xml
index 81d153c..a9488ee 100644
--- a/ring-android/app/src/main/res/layout/frag_call_list.xml
+++ b/ring-android/app/src/main/res/layout/frag_call_list.xml
@@ -77,6 +77,22 @@
         android:paddingTop="8dp"
         tools:listitem="@layout/item_calllist" />
 
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@android:id/empty"
+        android:layout_centerVertical="true"
+        android:layout_centerHorizontal="true" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text=""
+        android:id="@+id/emptyTextView"
+        android:layout_centerInParent="true"
+        android:layout_alignParentEnd="false"
+        android:gravity="center" />
+
     <android.support.design.widget.FloatingActionButton
         android:id="@+id/newconv_fab"
         android:layout_width="wrap_content"