diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4aeca16b2f..cdaa85d8e0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -214,7 +214,6 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
- android:exported="true"
android:parentActivityName=".ConversationListActivity">
+
+
diff --git a/res/mipmap-hdpi/ic_group_shortcut.png b/res/mipmap-hdpi/ic_group_shortcut.png
new file mode 100644
index 0000000000..aeb6937eb8
Binary files /dev/null and b/res/mipmap-hdpi/ic_group_shortcut.png differ
diff --git a/res/mipmap-hdpi/ic_person_shortcut.png b/res/mipmap-hdpi/ic_person_shortcut.png
new file mode 100644
index 0000000000..568ad9cb3e
Binary files /dev/null and b/res/mipmap-hdpi/ic_person_shortcut.png differ
diff --git a/res/mipmap-mdpi/ic_group_shortcut.png b/res/mipmap-mdpi/ic_group_shortcut.png
new file mode 100644
index 0000000000..d40965d067
Binary files /dev/null and b/res/mipmap-mdpi/ic_group_shortcut.png differ
diff --git a/res/mipmap-mdpi/ic_person_shortcut.png b/res/mipmap-mdpi/ic_person_shortcut.png
new file mode 100644
index 0000000000..cffe2437f5
Binary files /dev/null and b/res/mipmap-mdpi/ic_person_shortcut.png differ
diff --git a/res/mipmap-xhdpi/ic_group_shortcut.png b/res/mipmap-xhdpi/ic_group_shortcut.png
new file mode 100644
index 0000000000..7403c7e693
Binary files /dev/null and b/res/mipmap-xhdpi/ic_group_shortcut.png differ
diff --git a/res/mipmap-xhdpi/ic_person_shortcut.png b/res/mipmap-xhdpi/ic_person_shortcut.png
new file mode 100644
index 0000000000..f227ebfa39
Binary files /dev/null and b/res/mipmap-xhdpi/ic_person_shortcut.png differ
diff --git a/res/mipmap-xxhdpi/ic_group_shortcut.png b/res/mipmap-xxhdpi/ic_group_shortcut.png
new file mode 100644
index 0000000000..9692d8c16d
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_group_shortcut.png differ
diff --git a/res/mipmap-xxhdpi/ic_person_shortcut.png b/res/mipmap-xxhdpi/ic_person_shortcut.png
new file mode 100644
index 0000000000..b84d485c52
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_person_shortcut.png differ
diff --git a/res/mipmap-xxxhdpi/ic_group_shortcut.png b/res/mipmap-xxxhdpi/ic_group_shortcut.png
new file mode 100644
index 0000000000..2c98a54a19
Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_group_shortcut.png differ
diff --git a/res/mipmap-xxxhdpi/ic_person_shortcut.png b/res/mipmap-xxxhdpi/ic_person_shortcut.png
new file mode 100644
index 0000000000..a0e0212d0e
Binary files /dev/null and b/res/mipmap-xxxhdpi/ic_person_shortcut.png differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f9f8c7bca2..1eeed6991f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1232,6 +1232,7 @@
Leave group
All media
Conversation settings
+ Add to home screen
Expand popup
@@ -1390,7 +1391,6 @@
Signal is locked
TAP TO UNLOCK
Reminder:
- Add to home screen
About
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 771f989b2e..8e1870836e 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.TypedArray;
+import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.ColorDrawable;
@@ -39,6 +40,9 @@ import android.provider.ContactsContract;
import android.provider.Telephony;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.content.pm.ShortcutInfoCompat;
+import android.support.v4.content.pm.ShortcutManagerCompat;
+import android.support.v4.graphics.drawable.IconCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.WindowCompat;
import android.support.v7.app.ActionBar;
@@ -781,32 +785,47 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleAddShortcut() {
Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.getAddress());
- Intent launchIntent = new Intent(getApplicationContext(), ConversationActivity.class);
+ new AsyncTask() {
- launchIntent.putExtra(ADDRESS_EXTRA, recipient.getAddress().serialize());
- launchIntent.putExtra(TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
- launchIntent.setDataAndType(getIntent().getData(), getIntent().getType());
+ @Override
+ protected IconCompat doInBackground(Void... voids) {
+ Context context = getApplicationContext();
+ IconCompat icon = null;
- long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
+ if (recipient.getContactPhoto() != null) {
+ try {
+ icon = IconCompat.createWithAdaptiveBitmap(BitmapFactory.decodeStream(recipient.getContactPhoto().openInputStream(context)));
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to decode contact photo during shortcut creation. Falling back to generic icon.", e);
+ }
+ }
- launchIntent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
- launchIntent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
+ if (icon == null) {
+ icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut
+ : R.mipmap.ic_person_shortcut);
+ }
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return icon;
+ }
- Intent intent = new Intent();
- final String name = Optional.fromNullable(recipient.getProfileName())
- .or(Optional.fromNullable(recipient.getName()))
- .or(recipient.toShortString());
- // these constants are deprecated but their replacement (ShortcutManager) is available only from API level 25
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(getApplicationContext(), R.mipmap.ic_launcher));
- intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
+ @Override
+ protected void onPostExecute(IconCompat icon) {
+ Context context = getApplicationContext();
+ String name = Optional.fromNullable(recipient.getName())
+ .or(Optional.fromNullable(recipient.getProfileName()))
+ .or(recipient.toShortString());
- getApplicationContext().sendBroadcast(intent);
- Toast.makeText(this, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show();
+ ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis())
+ .setShortLabel(name)
+ .setIcon(icon)
+ .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress()))
+ .build();
+
+ if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) {
+ Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show();
+ }
+ }
+ }.execute();
}
private void handleLeavePushGroup() {
@@ -1380,7 +1399,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void initializeResources() {
if (recipient != null) recipient.removeListener(this);
- recipient = getRecipientFromExtras(getIntent(), this);
+
+ recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
@@ -1395,26 +1415,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
recipient.addListener(this);
}
- /**
- * Extracts the Recipient instance from the extras contained in the intent.
- *
- * This can be passed in two ways:
- *
- * - If the intent was started from inside the app, the address is a parcelable Address instance.
- * - If it was launched from the home screen then it is a serialised (stringified) form of the Address, as home screen
- * shortcuts cannot contain instances of Address (see BadParcelableException).
- */
- static Recipient getRecipientFromExtras(@NonNull Intent intent, @NonNull Context context) {
- Address address;
- final Address parcelableAddress = intent.getParcelableExtra(ADDRESS_EXTRA);
- if(parcelableAddress != null) {
- address = parcelableAddress;
- } else {
- address = Address.fromSerialized((String) intent.getExtras().get(ADDRESS_EXTRA));
- }
- return Recipient.from(context, address, true);
- }
-
private void initializeProfiles() {
if (!isSecureText) {
Log.i(TAG, "SMS contact, no profile fetch needed.");
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 5f256cd2a0..9ec757fc3c 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -206,7 +206,7 @@ public class ConversationFragment extends Fragment
}
private void initializeResources() {
- this.recipient = ConversationActivity.getRecipientFromExtras(getActivity().getIntent(), getActivity());
+ this.recipient = Recipient.from(getActivity(), getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true);
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
diff --git a/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java b/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java
new file mode 100644
index 0000000000..1c7e6de00d
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ShortcutLauncherActivity.java
@@ -0,0 +1,42 @@
+package org.thoughtcrime.securesms;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v7.app.AppCompatActivity;
+
+import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.util.CommunicationActions;
+
+public class ShortcutLauncherActivity extends AppCompatActivity {
+
+ private static final String KEY_SERIALIZED_ADDRESS = "serialized_address";
+
+ public static Intent createIntent(@NonNull Context context, @NonNull Address address) {
+ Intent intent = new Intent(context, ShortcutLauncherActivity.class);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.putExtra(KEY_SERIALIZED_ADDRESS, address.serialize());
+
+ return intent;
+ }
+
+ @SuppressLint("StaticFieldLeak")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String serializedAddress = getIntent().getStringExtra(KEY_SERIALIZED_ADDRESS);
+ Address address = Address.fromSerialized(serializedAddress);
+ Recipient recipient = Recipient.from(this, address, true);
+ TaskStackBuilder backStack = TaskStackBuilder.create(this)
+ .addNextIntent(new Intent(this, ConversationListActivity.class));
+
+ CommunicationActions.startConversation(this, recipient, null, backStack);
+ finish();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java
index 0e405de016..a8c0182d8b 100644
--- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java
+++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java
@@ -8,6 +8,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import org.thoughtcrime.securesms.ConversationActivity;
@@ -43,9 +44,14 @@ public class CommunicationActions {
.execute();
}
- public static void startConversation(@NonNull Context context,
- @NonNull Recipient recipient,
- @Nullable String text)
+ public static void startConversation(@NonNull Context context, @NonNull Recipient recipient, @Nullable String text) {
+ startConversation(context, recipient, text, null);
+ }
+
+ public static void startConversation(@NonNull Context context,
+ @NonNull Recipient recipient,
+ @Nullable String text,
+ @Nullable TaskStackBuilder backStack)
{
new AsyncTask() {
@Override
@@ -64,7 +70,12 @@ public class CommunicationActions {
intent.putExtra(ConversationActivity.TEXT_EXTRA, text);
}
- context.startActivity(intent);
+ if (backStack != null) {
+ backStack.addNextIntent(intent);
+ backStack.startActivities();
+ } else {
+ context.startActivity(intent);
+ }
}
}.execute();
}