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() {