conversation: add location sharing support

Change-Id: I656e67ea53125339de7db8094732b8767386bb25
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 759d55a..13f139e 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
@@ -20,15 +20,21 @@
 package cx.ring.fragments;
 
 import android.Manifest;
+import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.graphics.Typeface;
+import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.provider.MediaStore;
 import android.text.Editable;
 import android.text.TextUtils;
@@ -58,6 +64,8 @@
 import androidx.appcompat.widget.PopupMenu;
 import androidx.appcompat.widget.Toolbar;
 import androidx.core.view.ViewCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 
 import java.io.File;
@@ -90,11 +98,13 @@
 import cx.ring.model.Error;
 import cx.ring.model.Uri;
 import cx.ring.mvp.BaseSupportFragment;
+import cx.ring.services.LocationSharingService;
 import cx.ring.services.NotificationService;
 import cx.ring.utils.ActionHelper;
 import cx.ring.utils.AndroidFileUtils;
 import cx.ring.utils.ContentUriHandler;
 import cx.ring.utils.DeviceUtils;
+import cx.ring.utils.ConversationPath;
 import cx.ring.utils.MediaButtonsHelper;
 import cx.ring.views.AvatarDrawable;
 import io.reactivex.Completable;
@@ -107,7 +117,7 @@
 public class ConversationFragment extends BaseSupportFragment<ConversationPresenter> implements
         MediaButtonsHelper.MediaButtonsHelperCallback,
         ConversationView, SharedPreferences.OnSharedPreferenceChangeListener {
-    protected static final String TAG = ConversationFragment.class.getSimpleName();
+    private static final String TAG = ConversationFragment.class.getSimpleName();
 
     public static final int REQ_ADD_CONTACT = 42;
 
@@ -115,6 +125,7 @@
     public static final String KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + ".ACCOUNT_ID";
     public static final String KEY_PREFERENCE_PENDING_MESSAGE = "pendingMessage";
     public static final String KEY_PREFERENCE_CONVERSATION_COLOR = "color";
+    public static final String EXTRA_SHOW_MAP = "showMap";
 
     private static final int REQUEST_CODE_FILE_PICKER = 1000;
     private static final int REQUEST_PERMISSION_CAMERA = 1001;
@@ -123,13 +134,14 @@
     private static final int REQUEST_CODE_CAPTURE_AUDIO = 1004;
     private static final int REQUEST_CODE_CAPTURE_VIDEO = 1005;
 
+    private ServiceConnection locationServiceConnection = null;
+
     private FragConversationBinding binding;
     private MenuItem mAudioCallBtn = null;
     private MenuItem mVideoCallBtn = null;
 
     private View currentBottomView = null;
     private ConversationAdapter mAdapter = null;
-    private NumberAdapter mNumberAdapter = null;
     private int marginPx;
     private int marginPxTotal;
     private final ValueAnimator animation = new ValueAnimator();
@@ -142,7 +154,8 @@
     private int mSelectedPosition;
 
     private AvatarDrawable mConversationAvatar;
-    private Map<String, AvatarDrawable> mParticipantAvatars = new HashMap();
+    private Map<String, AvatarDrawable> mParticipantAvatars = new HashMap<>();
+    private int mapWidth, mapHeight;
 
     public AvatarDrawable getConversationAvatar(String uri) {
         return mParticipantAvatars.get(uri);
@@ -195,15 +208,22 @@
     }
 
     private void updateListPadding() {
-        if (currentBottomView != null && currentBottomView.getHeight() != 0)
+        if (currentBottomView != null && currentBottomView.getHeight() != 0) {
             setBottomPadding(binding.histList, currentBottomView.getHeight() + marginPxTotal);
+            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
+            params.bottomMargin = currentBottomView.getHeight() + marginPxTotal;
+            binding.mapCard.setLayoutParams(params);
+        }
     }
 
     @Nullable
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         injectFragment(((JamiApplication) getActivity().getApplication()).getRingInjectionComponent());
-        marginPx = getResources().getDimensionPixelSize(R.dimen.conversation_message_input_margin);
+        Resources res = getResources();
+        marginPx = res.getDimensionPixelSize(R.dimen.conversation_message_input_margin);
+        mapWidth = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_width);
+        mapHeight = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_height);
         marginPxTotal = marginPx;
 
         binding = FragConversationBinding.inflate(inflater, container, false);
@@ -242,6 +262,14 @@
                 .flatMapCompletable(this::sendFile)
                 .doFinally(contentInfo::releasePermission)));
         binding.msgInputTxt.setOnEditorActionListener((v, actionId, event) -> actionSendMsgText(actionId));
+        binding.msgInputTxt.setOnFocusChangeListener((view, hasFocus) -> {
+            if (hasFocus)  {
+                Fragment fragment = getChildFragmentManager().findFragmentById(R.id.mapLayout);
+                if (fragment != null) {
+                    ((LocationSharingFragment) fragment).hideControls();
+                }
+            }
+        });
         binding.msgInputTxt.addTextChangedListener(new TextWatcher() {
             @Override
             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -320,6 +348,13 @@
         animation.removeAllUpdateListeners();
         binding.histList.setAdapter(null);
         mCompositeDisposable.clear();
+        if (locationServiceConnection != null) {
+            try {
+                requireContext().unbindService(locationServiceConnection);
+            } catch (Exception e) {
+                Log.w(TAG, "Error unbinding service: " + e.getMessage());
+            }
+        }
         super.onDestroyView();
     }
 
@@ -351,6 +386,7 @@
         presenter.selectFile();
     }
 
+    @SuppressLint("RestrictedApi")
     public void expandMenu(View v) {
         Context context = requireContext();
         PopupMenu popup = new PopupMenu(context, v);
@@ -394,6 +430,9 @@
                 case R.id.conv_send_file:
                     selectFile();
                     break;
+                case R.id.conv_share_location:
+                    shareLocation();
+                    break;
             }
             return false;
         });
@@ -402,6 +441,36 @@
         menuHelper.show();
     }
 
+    public void shareLocation() {
+        presenter.shareLocation();
+    }
+
+    public void closeLocationSharing(boolean isSharing) {
+        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
+        if (params.width != mapWidth) {
+            params.width = mapWidth;
+            params.height = mapHeight;
+            binding.mapCard.setLayoutParams(params);
+        }
+        if (!isSharing)
+            hideMap();
+    }
+
+    public void openLocationSharing() {
+        binding.conversationLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
+        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
+        if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
+            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            binding.mapCard.setLayoutParams(params);
+        }
+    }
+
+    @Override
+    public void startShareLocation(String accountId, String conversationId) {
+        showMap(accountId, conversationId, true);
+    }
+
     /**
      * Used to update with the past adapter position when a long click was registered
      * @param position
@@ -410,6 +479,42 @@
         mSelectedPosition = position;
     }
 
+    @Override
+    public void showMap(String accountId, String contactId, boolean open)  {
+        if (binding.mapCard.getVisibility() == View.GONE) {
+            Log.w(TAG, "showMap " + accountId + " " + contactId);
+
+            FragmentManager fragmentManager = getChildFragmentManager();
+            LocationSharingFragment fragment = LocationSharingFragment.newInstance(accountId, contactId, open);
+            fragmentManager.beginTransaction()
+                    .add(R.id.mapLayout, fragment, "map")
+                    .commit();
+            binding.mapCard.setVisibility(View.VISIBLE);
+        }
+        if (open) {
+            Fragment fragment = getChildFragmentManager().findFragmentById(R.id.mapLayout);
+            if (fragment != null) {
+                ((LocationSharingFragment) fragment).showControls();
+            }
+        }
+    }
+
+    @Override
+    public void hideMap() {
+        if (binding.mapCard.getVisibility() != View.GONE) {
+            binding.mapCard.setVisibility(View.GONE);
+
+            FragmentManager fragmentManager = getChildFragmentManager();
+            Fragment fragment = fragmentManager.findFragmentById(R.id.mapLayout);
+
+            if (fragment != null) {
+                fragmentManager.beginTransaction()
+                        .remove(fragment)
+                        .commit();
+            }
+        }
+    }
+
     public void takePicture() {
         if (!presenter.getDeviceRuntimeService().hasVideoPermission()) {
             requestPermissions(new String[]{Manifest.permission.CAMERA}, JamiApplication.PERMISSIONS_REQUEST);
@@ -692,6 +797,35 @@
         } catch (Exception e) {
             Log.e(TAG, "Can't load conversation preferences");
         }
+
+        if (locationServiceConnection == null) {
+            locationServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    Log.w(TAG, "onServiceConnected");
+                    LocationSharingService.LocalBinder binder = (LocationSharingService.LocalBinder) service;
+                    LocationSharingService locationService = binder.getService();
+                    ConversationPath path = new ConversationPath(presenter.getPath());
+                    if (locationService.isSharing(path)) {
+                        showMap(accountId, contactUri.getUri(), false);
+                    }
+                    try {
+                        requireContext().unbindService(locationServiceConnection);
+                    } catch (Exception e) {
+                        Log.w(TAG, "Error unbinding service", e);
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    Log.w(TAG, "onServiceDisconnected");
+                    locationServiceConnection = null;
+                }
+            };
+
+            Log.w(TAG, "bindService");
+            requireContext().bindService(new Intent(requireContext(), LocationSharingService.class), locationServiceConnection, 0);
+        }
     }
 
     @Override
@@ -733,10 +867,8 @@
     @Override
     public void displayNumberSpinner(final Conversation conversation, final Uri number) {
         binding.numberSelector.setVisibility(View.VISIBLE);
-        mNumberAdapter = new NumberAdapter(getActivity(),
-                conversation.getContact(),
-                false);
-        binding.numberSelector.setAdapter(mNumberAdapter);
+        binding.numberSelector.setAdapter(new NumberAdapter(getActivity(),
+                conversation.getContact(), false));
         binding.numberSelector.setSelection(getIndex(binding.numberSelector, number));
     }
 
@@ -863,7 +995,7 @@
     }
 
     @Override
-    public void onPrepareOptionsMenu(Menu menu) {
+    public void onPrepareOptionsMenu(@NonNull Menu menu) {
         super.onPrepareOptionsMenu(menu);
         boolean visible = binding.cvMessageInput.getVisibility() == View.VISIBLE;
         if (mAudioCallBtn != null)
@@ -935,21 +1067,31 @@
     }
 
     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/plain")) {
-            binding.msgInputTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
-        } else {
-            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)
+        Log.w(TAG, "handleShareIntent " + intent);
+
+        String action = intent.getAction();
+        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            String type = intent.getType();
+            if (type == null) {
+                Log.w(TAG, "Can't share with no type");
                 return;
-            startFileSend(AndroidFileUtils.getCacheFile(requireContext(), uri).flatMapCompletable(this::sendFile));
+            }
+            if (type.startsWith("text/plain")) {
+                binding.msgInputTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
+            } else {
+                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;
+                startFileSend(AndroidFileUtils.getCacheFile(requireContext(), uri).flatMapCompletable(this::sendFile));
+            }
+        } else if (Intent.ACTION_VIEW.equals(action)) {
+            ConversationPath path = ConversationPath.fromIntent(intent);
+            if (path != null && intent.getBooleanExtra(EXTRA_SHOW_MAP, false)) {
+                shareLocation();
+            }
         }
     }
 
@@ -967,7 +1109,6 @@
         //Use Android Storage File Access to download the file
         Intent downloadFileIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
         downloadFileIntent.setType(AndroidFileUtils.getMimeTypeFromExtension(file.getExtension()));
-
         downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
         downloadFileIntent.putExtra(Intent.EXTRA_TITLE,file.getDisplayName());