implement direct sharing in android 6.0. fixes #1321

Daniel Gultsch created

Change summary

build.gradle                                                                   |  4 
src/main/AndroidManifest.xml                                                   |  9 
src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java | 87 
src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java                 | 87 
src/main/res/values/strings.xml                                                |  3 
5 files changed, 165 insertions(+), 25 deletions(-)

Detailed changes

build.gradle 🔗

@@ -48,11 +48,11 @@ dependencies {
 
 android {
 	compileSdkVersion 23
-	buildToolsVersion "23.0.1"
+	buildToolsVersion "23.0.2"
 
 	defaultConfig {
 		minSdkVersion 14
-		targetSdkVersion 21
+		targetSdkVersion 23
 		versionCode 109
 		versionName "1.8.0"
 		project.ext.set(archivesBaseName, archivesBaseName + "-" + versionName);

src/main/AndroidManifest.xml 🔗

@@ -136,6 +136,9 @@
 
                 <data android:mimeType="image/*"/>
             </intent-filter>
+            <meta-data
+                android:name="android.service.chooser.chooser_target_service"
+                android:value=".services.ContactChooserTargetService" />
         </activity>
         <activity
             android:name=".ui.TrustKeysActivity"
@@ -155,6 +158,12 @@
         </activity>
         <activity android:name="com.soundcloud.android.crop.CropImageActivity" />
         <service android:name=".services.ExportLogsService"/>
+        <service android:name=".services.ContactChooserTargetService"
+                 android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.chooser.ChooserTargetService" />
+            </intent-filter>
+        </service>
     </application>
 
 </manifest>

src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java 🔗

@@ -0,0 +1,87 @@
+package eu.siacs.conversations.services;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.service.chooser.ChooserTarget;
+import android.service.chooser.ChooserTargetService;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.ui.ShareWithActivity;
+
+@TargetApi(Build.VERSION_CODES.M)
+public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection {
+
+	private final Object lock = new Object();
+
+	private XmppConnectionService mXmppConnectionService;
+
+	private final int MAX_TARGETS = 5;
+
+	@Override
+	public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
+		Intent intent = new Intent(this, XmppConnectionService.class);
+		intent.setAction("contact_chooser");
+		startService(intent);
+		bindService(intent, this, Context.BIND_AUTO_CREATE);
+		ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
+		try {
+			waitForService();
+			final ArrayList<Conversation> conversations = new ArrayList<>();
+			if (!mXmppConnectionService.areMessagesInitialized()) {
+				return chooserTargets;
+			}
+			mXmppConnectionService.populateWithOrderedConversations(conversations, false);
+			final ComponentName componentName = new ComponentName(this, ShareWithActivity.class);
+			final int pixel = (int) (48 * getResources().getDisplayMetrics().density);
+			for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) {
+				final Conversation conversation = conversations.get(i);
+				final String name = conversation.getName();
+				final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel));
+				final float score = (1.0f / MAX_TARGETS) * i;
+				final Bundle extras = new Bundle();
+				extras.putString("uuid", conversation.getUuid());
+				chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras));
+			}
+		} catch (InterruptedException e) {
+		}
+		unbindService(this);
+		return chooserTargets;
+	}
+
+	@Override
+	public void onServiceConnected(ComponentName name, IBinder service) {
+		XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service;
+		mXmppConnectionService = binder.getService();
+		synchronized (this.lock) {
+			lock.notifyAll();
+		}
+	}
+
+	@Override
+	public void onServiceDisconnected(ComponentName name) {
+		mXmppConnectionService = null;
+	}
+
+	private void waitForService() throws InterruptedException {
+		if (mXmppConnectionService == null) {
+			synchronized (this.lock) {
+				lock.wait();
+			}
+		}
+	}
+}

src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java 🔗

@@ -4,6 +4,7 @@ import android.app.PendingIntent;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -17,6 +18,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Conversation;
@@ -33,6 +35,7 @@ public class ShareWithActivity extends XmppActivity {
 		public String account;
 		public String contact;
 		public String text;
+		public String uuid;
 	}
 
 	private Share share;
@@ -40,6 +43,7 @@ public class ShareWithActivity extends XmppActivity {
 	private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
 	private ListView mListView;
 	private List<Conversation> mConversations = new ArrayList<>();
+	private Toast mToast;
 
 	private UiCallback<Message> attachFileCallback = new UiCallback<Message>() {
 
@@ -50,8 +54,22 @@ public class ShareWithActivity extends XmppActivity {
 		}
 
 		@Override
-		public void success(Message message) {
+		public void success(final Message message) {
 			xmppConnectionService.sendMessage(message);
+			runOnUiThread(new Runnable() {
+				@Override
+				public void run() {
+					if (mToast != null) {
+						mToast.cancel();
+					}
+					if (share.uuid != null) {
+						mToast = Toast.makeText(getApplicationContext(),
+								getString(share.image ? R.string.shared_image_with_x : R.string.shared_file_with_x,message.getConversation().getName()),
+								Toast.LENGTH_SHORT);
+						mToast.show();
+					}
+				}
+			});
 		}
 
 		@Override
@@ -128,6 +146,8 @@ public class ShareWithActivity extends XmppActivity {
 			return;
 		}
 		final String type = intent.getType();
+		Log.d(Config.LOGTAG, "action: "+intent.getAction()+ ", type:"+type);
+		share.uuid = intent.getStringExtra("uuid");
 		if (Intent.ACTION_SEND.equals(intent.getAction())) {
 			final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
 			if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
@@ -146,7 +166,11 @@ public class ShareWithActivity extends XmppActivity {
 			this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
 		}
 		if (xmppConnectionServiceBound) {
-			xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
+			if (share.uuid != null) {
+				share();
+			} else {
+				xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
+			}
 		}
 
 	}
@@ -163,7 +187,7 @@ public class ShareWithActivity extends XmppActivity {
 	@Override
 	void onBackendConnected() {
 		if (xmppConnectionServiceBound && share != null
-				&& share.contact != null && share.account != null) {
+				&& ((share.contact != null && share.account != null) || share.uuid != null)) {
 			share();
 			return;
 		}
@@ -172,28 +196,41 @@ public class ShareWithActivity extends XmppActivity {
 	}
 
 	private void share() {
-		Account account;
-		try {
-			account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account));
-		} catch (final InvalidJidException e) {
-			account = null;
-		}
-		if (account == null) {
-			return;
-		}
 		final Conversation conversation;
-		try {
-			conversation = xmppConnectionService
-					.findOrCreateConversation(account, Jid.fromString(share.contact), false);
-		} catch (final InvalidJidException e) {
-			return;
+		if (share.uuid != null) {
+			conversation = xmppConnectionService.findConversationByUuid(share.uuid);
+			if (conversation == null) {
+				return;
+			}
+		}else{
+			Account account;
+			try {
+				account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account));
+			} catch (final InvalidJidException e) {
+				account = null;
+			}
+			if (account == null) {
+				return;
+			}
+
+			try {
+				conversation = xmppConnectionService
+						.findOrCreateConversation(account, Jid.fromString(share.contact), false);
+			} catch (final InvalidJidException e) {
+				return;
+			}
 		}
 		share(conversation);
 	}
 
 	private void share(final Conversation conversation) {
 		if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) {
-			showInstallPgpDialog();
+			if (share.uuid == null) {
+				showInstallPgpDialog();
+			} else {
+				Toast.makeText(this,R.string.openkeychain_not_installed,Toast.LENGTH_SHORT).show();
+				finish();
+			}
 			return;
 		}
 		if (share.uris.size() != 0) {
@@ -201,23 +238,27 @@ public class ShareWithActivity extends XmppActivity {
 				@Override
 				public void onPresenceSelected() {
 					if (share.image) {
-						Toast.makeText(getApplicationContext(),
+						mToast = Toast.makeText(getApplicationContext(),
 								getText(R.string.preparing_image),
-								Toast.LENGTH_LONG).show();
+								Toast.LENGTH_LONG);
+						mToast.show();
 						for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
 							ShareWithActivity.this.xmppConnectionService
 									.attachImageToConversation(conversation, i.next(),
 											attachFileCallback);
 						}
 					} else {
-						Toast.makeText(getApplicationContext(),
+						mToast = Toast.makeText(getApplicationContext(),
 								getText(R.string.preparing_file),
-								Toast.LENGTH_LONG).show();
+								Toast.LENGTH_LONG);
+						mToast.show();
 						ShareWithActivity.this.xmppConnectionService
 								.attachFileToConversation(conversation, share.uris.get(0),
 										attachFileCallback);
 					}
-					switchToConversation(conversation, null, true);
+					if (share.uuid == null) {
+						switchToConversation(conversation, null, true);
+					}
 					finish();
 				}
 			};

src/main/res/values/strings.xml 🔗

@@ -91,6 +91,7 @@
 	<string name="openkeychain_required_long">Conversations utilizes a third party app called <b>OpenKeychain</b> to encrypt and decrypt messages and to manage your public keys.\n\nOpenKeychain is licensed under GPLv3 and available on F-Droid and Google Play.\n\n<small>(Please restart Conversations afterwards.)</small></string>
 	<string name="restart">Restart</string>
 	<string name="install">Install</string>
+	<string name="openkeychain_not_installed">Please install OpenKeychain</string>
 	<string name="offering">offering…</string>
 	<string name="waiting">waiting…</string>
 	<string name="no_pgp_key">No OpenPGP Key found</string>
@@ -554,4 +555,6 @@
 		<item quantity="one">%d message</item>
 		<item quantity="other">%d messages</item>
 	</plurals>
+	<string name="shared_file_with_x">Shared file with %s</string>
+	<string name="shared_image_with_x">Shared image with %s</string>
 </resources>