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(); }