Launch stickers.

master
Greyson Parrelli 2019-12-13 01:23:32 -05:00
parent 4173efbe5a
commit b9bd3f2b4c
25 changed files with 434 additions and 116 deletions

View File

@ -188,6 +188,14 @@
<data android:scheme="sgnl"
android:host="addstickers" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="signal.art"
android:pathPrefix="/addstickers"/>
</intent-filter>
</activity>
<activity-alias android:name=".RoutingActivity"
@ -248,7 +256,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ExperienceUpgradeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/stickers_experience_animation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="13dp"
android:layout_marginBottom="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/stickers_experience_title"
app:lottie_rawRes="@raw/lottie_stickers_splash"/>
<TextView
android:id="@+id/stickers_experience_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
style="@style/Signal.Text.Headline"
android:fontFamily="sans-serif-medium"
android:text="@string/ExperienceUpgradeActivity_introducing_stickers"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/stickers_experience_subtitle"
app:layout_constraintStart_toStartOf="@id/stickers_experience_start_margin"
app:layout_constraintEnd_toEndOf="@id/stickers_experience_end_margin"/>
<TextView
android:id="@+id/stickers_experience_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
style="@style/Signal.Text.Body"
android:text="@string/ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers_tap_this_icon"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/stickers_experience_icon"
app:layout_constraintStart_toStartOf="@id/stickers_experience_start_margin"
app:layout_constraintEnd_toEndOf="@id/stickers_experience_end_margin"/>
<ImageView
android:id="@+id/stickers_experience_icon"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginBottom="32dp"
app:srcCompat="@drawable/ic_sticker_splash_24"
app:layout_constraintBottom_toTopOf="@id/stickers_experience_go_button"
app:layout_constraintStart_toStartOf="@id/stickers_experience_start_margin"
app:layout_constraintEnd_toEndOf="@id/stickers_experience_end_margin"/>
<com.dd.CircularProgressButton
android:id="@+id/stickers_experience_go_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="32dp"
android:background="@color/signal_primary"
android:textColor="@color/white"
android:elevation="4dp"
app:cpb_colorIndicator="@color/white"
app:cpb_colorProgress="@color/textsecure_primary"
app:cpb_cornerRadius="4dp"
app:cpb_selectorIdle="@drawable/progress_button_state"
app:cpb_textIdle="@string/ExperienceUpgradeActivity_lets_go"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/stickers_experience_start_margin"
app:layout_constraintEnd_toEndOf="@id/stickers_experience_end_margin"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/stickers_experience_start_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/stickers_experience_end_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -69,7 +69,7 @@
android:id="@+id/sticker_install_share_button_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/core_grey_90"
android:tint="?sticker_management_action_button_color"
android:visibility="gone"
app:srcCompat="@drawable/ic_forward_outline_24"
app:layout_constraintTop_toTopOf="@id/sticker_install_share_button"

File diff suppressed because one or more lines are too long

View File

@ -391,6 +391,11 @@
<string name="ExperienceUpgradeActivity_you_can_disable_or_enable_this_feature_link_previews">You can disable or enable this feature anytime in your Signal settings (Privacy &gt; Send link previews).</string>
<string name="ExperienceUpgradeActivity_got_it">Got it</string>
<string name="ExperienceUpgradeActivity_introducing_stickers">Introducing stickers</string>
<string name="ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers">Why use words when you can use stickers?</string>
<string name="ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers_tap_this_icon">Why use words when you can use stickers? Tap this icon on your keyboard:</string>
<string name="ExperienceUpgradeActivity_lets_go">Let\'s go</string>
<!-- GcmBroadcastReceiver -->
<string name="GcmBroadcastReceiver_retrieving_a_message">Retrieving a message…</string>

View File

@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log;
@ -68,8 +69,10 @@ import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.util.FrameRateTracker;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
@ -112,8 +115,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
initializeSecurityProvider();
initializeLogging();
initializeCrashHandling();
initializeFirstEverAppLaunch();
initializeAppDependencies();
initializeFirstEverAppLaunch();
initializeApplicationMigrations();
initializeMessageRetrieval();
initializeExpiringMessageManager();
@ -237,6 +240,10 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
InsightsOptOut.userRequestedOptOut(this);
TextSecurePreferences.setAppMigrationVersion(this, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(this, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCanonicalVersionCode());
TextSecurePreferences.setHasSeenStickerIntroTooltip(this, true);
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
}
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);

View File

@ -18,8 +18,12 @@ import com.melnykov.fab.FloatingActionButton;
import com.nineoldandroids.animation.ArgbEvaluator;
import org.thoughtcrime.securesms.IntroPagerAdapter.IntroPage;
import org.thoughtcrime.securesms.database.model.Sticker;
import org.thoughtcrime.securesms.experienceupgrades.StickersIntroFragment;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -29,11 +33,17 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.List;
public class ExperienceUpgradeActivity extends BaseActionBarActivity implements TypingIndicatorIntroFragment.Controller, LinkPreviewsIntroFragment.Controller {
public class ExperienceUpgradeActivity extends BaseActionBarActivity
implements TypingIndicatorIntroFragment.Controller,
LinkPreviewsIntroFragment.Controller,
StickersIntroFragment.Controller
{
private static final String TAG = ExperienceUpgradeActivity.class.getSimpleName();
private static final String DISMISS_ACTION = "org.thoughtcrime.securesms.ExperienceUpgradeActivity.DISMISS_ACTION";
private static final int NOTIFICATION_ID = 1339;
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private enum ExperienceUpgrade {
SIGNAL_REBRANDING(157,
new IntroPage(0xFF2090EA,
@ -86,8 +96,15 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
R.string.ExperienceUpgradeActivity_introducing_link_previews,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
null,
true);
null,
true),
STICKERS(580,
new IntroPage(0xFF2090EA, StickersIntroFragment.newInstance()),
R.string.ExperienceUpgradeActivity_introducing_stickers,
R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
null,
true);
private int version;
private List<IntroPage> pages;
@ -157,7 +174,7 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStatusBarColor(getResources().getColor(R.color.signal_primary_dark));
dynamicTheme.onCreate(this);
final Optional<ExperienceUpgrade> upgrade = getExperienceUpgrade(this);
if (!upgrade.isPresent()) {
@ -182,6 +199,12 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
private void onContinue(Optional<ExperienceUpgrade> seenUpgrade) {
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
int latestVersion = seenUpgrade.isPresent() ? seenUpgrade.get().getVersion()
@ -232,29 +255,9 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
onContinue(Optional.of(ExperienceUpgrade.LINK_PREVIEWS));
}
private final class OnPageChangeListener implements ViewPager.OnPageChangeListener {
private final ArgbEvaluator evaluator = new ArgbEvaluator();
private final ExperienceUpgrade upgrade;
public OnPageChangeListener(ExperienceUpgrade upgrade) {
this.upgrade = upgrade;
}
@Override
public void onPageSelected(int position) {}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final int nextPosition = (position + 1) % upgrade.getPages().size();
final int color = (Integer)evaluator.evaluate(positionOffset,
upgrade.getPage(position).backgroundColor,
upgrade.getPage(nextPosition).backgroundColor);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
}
@Override
public void onStickersFinished() {
onContinue(Optional.of(ExperienceUpgrade.STICKERS));
}
public static class AppUpgradeReceiver extends BroadcastReceiver {

View File

@ -330,9 +330,11 @@ public class ConversationFragment extends Fragment
if (!isTypingIndicatorShowing() && isAtBottom()) {
Context context = requireContext();
list.setVerticalScrollBarEnabled(false);
list.post(() -> { if (!isReacting) {
getListLayoutManager().smoothScrollToPosition(context, 0, 250);
}});
list.post(() -> {
if (!isReacting) {
getListLayoutManager().smoothScrollToPosition(context, 0, 250);
}
});
list.postDelayed(() -> list.setVerticalScrollBarEnabled(true), 300);
adapter.setHeaderView(typingView);
adapter.notifyItemInserted(0);
@ -346,8 +348,10 @@ public class ConversationFragment extends Fragment
}
}
} else {
if (getListLayoutManager().findFirstCompletelyVisibleItemPosition() == 0 && getListLayoutManager().getItemCount() > 1 && !replacedByIncomingMessage) {
if (!isReacting) getListLayoutManager().smoothScrollToPosition(requireContext(), 1, 250);
if (isTypingIndicatorShowing() && getListLayoutManager().findFirstCompletelyVisibleItemPosition() == 0 && getListLayoutManager().getItemCount() > 1 && !replacedByIncomingMessage) {
if (!isReacting) {
getListLayoutManager().smoothScrollToPosition(requireContext(), 1, 250);
}
list.setVerticalScrollBarEnabled(false);
list.postDelayed(() -> {
adapter.setHeaderView(null);

View File

@ -78,7 +78,7 @@ public class StickerDatabase extends Database {
this.attachmentSecret = attachmentSecret;
}
public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream) throws IOException {
public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream, boolean notify) throws IOException {
FileInfo fileInfo = saveStickerImage(dataStream);
ContentValues contentValues = new ContentValues();
@ -102,7 +102,7 @@ public class StickerDatabase extends Database {
if (sticker.isCover()) {
notifyStickerPackListeners();
if (sticker.isInstalled()) {
if (sticker.isInstalled() && notify) {
broadcastInstallEvent(sticker.getPackId());
}
}
@ -226,8 +226,8 @@ public class StickerDatabase extends Database {
notifyStickerPackListeners();
}
public void markPackAsInstalled(@NonNull String packKey) {
updatePackInstalled(databaseHelper.getWritableDatabase(), packKey, true);
public void markPackAsInstalled(@NonNull String packKey, boolean notify) {
updatePackInstalled(databaseHelper.getWritableDatabase(), packKey, true, notify);
notifyStickerPackListeners();
}
@ -273,7 +273,7 @@ public class StickerDatabase extends Database {
db.beginTransaction();
try {
updatePackInstalled(db, packId, false);
updatePackInstalled(db, packId, false, false);
deleteStickersInPackExceptCover(db, packId);
db.setTransactionSuccessful();
@ -284,7 +284,7 @@ public class StickerDatabase extends Database {
}
}
private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed) {
private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed, boolean notify) {
StickerPackRecord existing = getStickerPack(packId);
if (existing != null && existing.isInstalled() == installed) {
@ -298,7 +298,7 @@ public class StickerDatabase extends Database {
values.put(INSTALLED, installed ? 1 : 0);
db.update(TABLE_NAME, values, selection, args);
if (installed) {
if (installed && notify) {
broadcastInstallEvent(packId);
}
}

View File

@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.Closeable;
import java.io.IOException;
@ -648,7 +649,7 @@ public class ThreadDatabase extends Database {
if (!record.isMms() || record.isMmsNotification() || record.isGroupAction()) return null;
SlideDeck slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck();
Slide thumbnail = slideDeck.getThumbnailSlide();
Slide thumbnail = Optional.fromNullable(slideDeck.getThumbnailSlide()).or(Optional.fromNullable(slideDeck.getStickerSlide())).orNull();
if (thumbnail != null && !((MmsMessageRecord) record).isViewOnce()) {
return thumbnail.getThumbnailUri();

View File

@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.experienceupgrades;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.airbnb.lottie.LottieAnimationView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.TypingIndicatorView;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class StickersIntroFragment extends Fragment {
private Controller controller;
public static StickersIntroFragment newInstance() {
return new StickersIntroFragment();
}
public StickersIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(getActivity() instanceof Controller)) {
throw new IllegalStateException("Parent activity must implement the Controller interface.");
}
controller = (Controller) getActivity();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.experience_upgrade_stickers_fragment, container, false);
View goButton = view.findViewById(R.id.stickers_experience_go_button);
((LottieAnimationView) view.findViewById(R.id.stickers_experience_animation)).playAnimation();
goButton.setOnClickListener(v -> controller.onStickersFinished());
return view;
}
public interface Controller {
void onStickersFinished();
}
}

View File

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.migrations.MigrationCompleteJob;
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob;
import org.thoughtcrime.securesms.migrations.UuidMigrationJob;
import java.util.Arrays;
@ -94,12 +95,13 @@ public final class JobManagerFactories {
// Migrations
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
put(LegacyMigrationJob.KEY, new LegacyMigrationJob.Factory());
put(MigrationCompleteJob.KEY, new MigrationCompleteJob.Factory());
put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory());
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());
put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory());
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
// Dead jobs

View File

@ -565,7 +565,7 @@ public class PushDecryptJob extends BaseJob {
switch (operation.getType().get()) {
case INSTALL:
jobManager.add(new StickerPackDownloadJob(packId, packKey, false));
jobManager.add(StickerPackDownloadJob.forInstall(packId, packKey, true));
break;
case REMOVE:
DatabaseFactory.getStickerDatabase(context).uninstallPack(packId);

View File

@ -31,20 +31,43 @@ public class StickerDownloadJob extends BaseJob {
private static final String KEY_EMOJI = "emoji";
private static final String KEY_COVER = "cover";
private static final String KEY_INSTALLED = "installed";
private static final String KEY_NOTIFY = "notify";
private final IncomingSticker sticker;
private final boolean notify;
public StickerDownloadJob(@NonNull IncomingSticker sticker) {
StickerDownloadJob(@NonNull IncomingSticker sticker, boolean notify) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setLifespan(TimeUnit.DAYS.toMillis(30))
.build(),
sticker);
sticker,
notify);
}
private StickerDownloadJob(@NonNull Job.Parameters parameters, @NonNull IncomingSticker sticker) {
private StickerDownloadJob(@NonNull Job.Parameters parameters, @NonNull IncomingSticker sticker, boolean notify) {
super(parameters);
this.sticker = sticker;
this.notify = notify;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_PACK_ID, sticker.getPackId())
.putString(KEY_PACK_KEY, sticker.getPackKey())
.putString(KEY_PACK_TITLE, sticker.getPackTitle())
.putString(KEY_PACK_AUTHOR, sticker.getPackAuthor())
.putInt(KEY_STICKER_ID, sticker.getStickerId())
.putString(KEY_EMOJI, sticker.getEmoji())
.putBoolean(KEY_COVER, sticker.isCover())
.putBoolean(KEY_INSTALLED, sticker.isInstalled())
.putBoolean(KEY_NOTIFY, notify)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
@ -66,7 +89,7 @@ public class StickerDownloadJob extends BaseJob {
byte[] packKeyBytes = Hex.fromStringCondensed(sticker.getPackKey());
InputStream stream = receiver.retrieveSticker(packIdBytes, packKeyBytes, sticker.getStickerId());
db.insertSticker(sticker, stream);
db.insertSticker(sticker, stream, notify);
}
@Override
@ -74,24 +97,6 @@ public class StickerDownloadJob extends BaseJob {
return e instanceof PushNetworkException;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_PACK_ID, sticker.getPackId())
.putString(KEY_PACK_KEY, sticker.getPackKey())
.putString(KEY_PACK_TITLE, sticker.getPackTitle())
.putString(KEY_PACK_AUTHOR, sticker.getPackAuthor())
.putInt(KEY_STICKER_ID, sticker.getStickerId())
.putString(KEY_EMOJI, sticker.getEmoji())
.putBoolean(KEY_COVER, sticker.isCover())
.putBoolean(KEY_INSTALLED, sticker.isInstalled())
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to download sticker!");
@ -109,7 +114,7 @@ public class StickerDownloadJob extends BaseJob {
data.getBoolean(KEY_COVER),
data.getBoolean(KEY_INSTALLED));
return new StickerDownloadJob(parameters, sticker);
return new StickerDownloadJob(parameters, sticker, data.getBoolean(KEY_NOTIFY));
}
}
}

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.core.util.Preconditions;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.model.IncomingSticker;
@ -34,27 +33,46 @@ public class StickerPackDownloadJob extends BaseJob {
private static final String KEY_PACK_ID = "pack_key";
private static final String KEY_PACK_KEY = "pack_id";
private static final String KEY_REFERENCE_PACK = "reference_pack";
private static final String KEY_NOTIFY = "notify";
private final String packId;
private final String packKey;
private final boolean isReferencePack;
private final boolean notify;
public StickerPackDownloadJob(@NonNull String packId, @NonNull String packKey, boolean isReferencePack)
/**
* Downloads all the stickers in a pack.
* @param notify Whether or not a tooltip will be shown indicating the pack was installed.
*/
public static @NonNull StickerPackDownloadJob forInstall(@NonNull String packId, @NonNull String packKey, boolean notify) {
return new StickerPackDownloadJob(packId, packKey, false, notify);
}
/**
* Just installs a reference to the pack -- i.e. just the cover.
*/
public static @NonNull StickerPackDownloadJob forReference(@NonNull String packId, @NonNull String packKey) {
return new StickerPackDownloadJob(packId, packKey, true, true);
}
private StickerPackDownloadJob(@NonNull String packId, @NonNull String packKey, boolean isReferencePack, boolean notify)
{
this(new Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setQueue("StickerPackDownloadJob_" + packKey)
.build(),
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(30))
.setQueue("StickerPackDownloadJob_" + packId)
.build(),
packId,
packKey,
isReferencePack);
isReferencePack,
notify);
}
private StickerPackDownloadJob(@NonNull Parameters parameters,
@NonNull String packId,
@NonNull String packKey,
boolean isReferencePack)
boolean isReferencePack,
boolean notify)
{
super(parameters);
@ -64,6 +82,21 @@ public class StickerPackDownloadJob extends BaseJob {
this.packId = packId;
this.packKey = packKey;
this.isReferencePack = isReferencePack;
this.notify = notify;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_PACK_ID, packId)
.putString(KEY_PACK_KEY, packKey)
.putBoolean(KEY_REFERENCE_PACK, isReferencePack)
.putBoolean(KEY_NOTIFY, notify)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
@ -86,12 +119,12 @@ public class StickerPackDownloadJob extends BaseJob {
SignalServiceStickerManifest manifest = receiver.retrieveStickerManifest(packIdBytes, packKeyBytes);
if (manifest.getStickers().isEmpty()) {
Log.w(TAG, "No stickers in pack!");
Log.w(TAG, "No stickers in pack!");
return;
}
if (!isReferencePack && stickerDatabase.isPackAvailableAsReference(packId)) {
stickerDatabase.markPackAsInstalled(packId);
stickerDatabase.markPackAsInstalled(packId, notify);
}
StickerInfo cover = manifest.getCover().or(manifest.getStickers().get(0));
@ -102,7 +135,8 @@ public class StickerPackDownloadJob extends BaseJob {
cover.getId(),
"",
true,
!isReferencePack)));
!isReferencePack),
notify));
@ -117,7 +151,8 @@ public class StickerPackDownloadJob extends BaseJob {
stickerInfo.getId(),
stickerInfo.getEmoji(),
false,
true)));
true),
notify));
}
chain.then(jobs);
@ -131,22 +166,11 @@ public class StickerPackDownloadJob extends BaseJob {
return e instanceof PushNetworkException;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_PACK_ID, packId)
.putString(KEY_PACK_KEY, packKey)
.putBoolean(KEY_REFERENCE_PACK, isReferencePack)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to download manifest with pack_id: " + packId);
Log.w(TAG, "Failed to download manifest! Uninstalling pack.");
DatabaseFactory.getStickerDatabase(context).uninstallPack(packId);
DatabaseFactory.getStickerDatabase(context).deleteOrphanedPacks();
}
public static final class Factory implements Job.Factory<StickerPackDownloadJob> {
@ -154,9 +178,10 @@ public class StickerPackDownloadJob extends BaseJob {
public @NonNull
StickerPackDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new StickerPackDownloadJob(parameters,
data.getString(KEY_PACK_ID),
data.getString(KEY_PACK_KEY),
data.getBoolean(KEY_REFERENCE_PACK));
data.getString(KEY_PACK_ID),
data.getString(KEY_PACK_KEY),
data.getBoolean(KEY_REFERENCE_PACK),
data.getBoolean(KEY_NOTIFY));
}
}
}

View File

@ -23,7 +23,6 @@ public final class LinkPreviewUtil {
private static final Pattern DOMAIN_PATTERN = Pattern.compile("^(https?://)?([^/]+).*$");
private static final Pattern ALL_ASCII_PATTERN = Pattern.compile("^[\\x00-\\x7F]*$");
private static final Pattern ALL_NON_ASCII_PATTERN = Pattern.compile("^[^\\x00-\\x7F]*$");
private static final Pattern STICKER_URL_PATTERN = Pattern.compile("^.*#pack_id=(.*)&pack_key=(.*)$");
/**
* @return All whitelisted URLs in the source text.

View File

@ -38,7 +38,7 @@ public class ApplicationMigrations {
private static final int LEGACY_CANONICAL_VERSION = 455;
public static final int CURRENT_VERSION = 7;
public static final int CURRENT_VERSION = 8;
private static final class Version {
static final int LEGACY = 1;
@ -48,6 +48,7 @@ public class ApplicationMigrations {
static final int AVATAR_MIGRATION = 5;
static final int UUIDS = 6;
static final int CACHED_ATTACHMENTS = 7;
static final int STICKERS_LAUNCH = 8;
}
/**
@ -188,6 +189,10 @@ public class ApplicationMigrations {
jobs.put(Version.CACHED_ATTACHMENTS, new CachedAttachmentsMigrationJob());
}
if (lastSeenVersion < Version.STICKERS_LAUNCH) {
jobs.put(Version.STICKERS_LAUNCH, new StickerLaunchMigrationJob());
}
return jobs;
}

View File

@ -0,0 +1,73 @@
package org.thoughtcrime.securesms.migrations;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.MultiDeviceStickerPackOperationJob;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class StickerLaunchMigrationJob extends MigrationJob {
public static final String KEY = "StickerLaunchMigrationJob";
StickerLaunchMigrationJob() {
this(new Parameters.Builder().build());
}
private StickerLaunchMigrationJob(@NonNull Parameters parameters) {
super(parameters);
}
@Override
public boolean isUiBlocking() {
return false;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void performMigration() {
installPack(context, BlessedPacks.ZOZO);
installPack(context, BlessedPacks.BANDIT);
}
@Override
boolean shouldRetry(@NonNull Exception e) {
return false;
}
private static void installPack(@NonNull Context context, @NonNull BlessedPacks.Pack pack) {
JobManager jobManager = ApplicationDependencies.getJobManager();
StickerDatabase stickerDatabase = DatabaseFactory.getStickerDatabase(context);
if (stickerDatabase.isPackAvailableAsReference(pack.getPackId())) {
stickerDatabase.markPackAsInstalled(pack.getPackId(), false);
}
jobManager.add(StickerPackDownloadJob.forInstall(pack.getPackId(), pack.getPackKey(), false));
if (TextSecurePreferences.isMultiDevice(context)) {
jobManager.add(new MultiDeviceStickerPackOperationJob(pack.getPackId(), pack.getPackKey(), MultiDeviceStickerPackOperationJob.Type.INSTALL));
}
}
public static class Factory implements Job.Factory<StickerLaunchMigrationJob> {
@Override
public @NonNull
StickerLaunchMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new StickerLaunchMigrationJob(parameters);
}
}
}

View File

@ -10,9 +10,33 @@ import java.util.Set;
*/
public final class BlessedPacks {
private static final Set<String> BLESSED_PACK_IDS = new HashSet<>();
public static final Pack ZOZO = new Pack("fb535407d2f6497ec074df8b9c51dd1d", "17e971c134035622781d2ee249e6473b774583750b68c11bb82b7509c68b6dfd");
public static final Pack BANDIT = new Pack("9acc9e8aba563d26a4994e69263e3b25", "5a6dff3948c28efb9b7aaf93ecc375c69fc316e78077ed26867a14d10a0f6a12");
private static final Set<String> BLESSED_PACK_IDS = new HashSet<String>() {{
add(ZOZO.getPackId());
add(BANDIT.getPackId());
}};
public static boolean contains(@NonNull String packId) {
return BLESSED_PACK_IDS.contains(packId);
}
public static class Pack {
private final String packId;
private final String packKey;
public Pack(@NonNull String packId, @NonNull String packKey) {
this.packId = packId;
this.packKey = packKey;
}
public @NonNull String getPackId() {
return packId;
}
public @NonNull String getPackKey() {
return packKey;
}
}
}

View File

@ -45,7 +45,7 @@ final class StickerManagementRepository {
String packId = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.STICKER_PACK_ID));
String packKey = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.STICKER_PACK_KEY));
jobManager.add(new StickerPackDownloadJob(packId, packKey, true));
jobManager.add(StickerPackDownloadJob.forReference(packId, packKey));
}
}
});
@ -84,15 +84,15 @@ final class StickerManagementRepository {
});
}
void installStickerPack(@NonNull String packId, @NonNull String packKey) {
void installStickerPack(@NonNull String packId, @NonNull String packKey, boolean notify) {
SignalExecutors.SERIAL.execute(() -> {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (stickerDatabase.isPackAvailableAsReference(packId)) {
stickerDatabase.markPackAsInstalled(packId);
stickerDatabase.markPackAsInstalled(packId, notify);
}
jobManager.add(new StickerPackDownloadJob(packId, packKey, false));
jobManager.add(StickerPackDownloadJob.forInstall(packId, packKey, notify));
if (TextSecurePreferences.isMultiDevice(context)) {
jobManager.add(new MultiDeviceStickerPackOperationJob(packId, packKey, MultiDeviceStickerPackOperationJob.Type.INSTALL));

View File

@ -53,7 +53,7 @@ final class StickerManagementViewModel extends ViewModel {
}
void onStickerPackInstallClicked(@NonNull String packId, @NonNull String packKey) {
repository.installStickerPack(packId, packKey);
repository.installStickerPack(packId, packKey, false);
}
@Override

View File

@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
@ -76,7 +77,7 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActionBa
protected void onCreate(Bundle savedInstanceState, boolean ready) {
setContentView(R.layout.sticker_preview_activity);
Optional<Pair<String, String>> stickerParams = StickerUrl.parseActionUri(getIntent().getData());
Optional<Pair<String, String>> stickerParams = StickerUrl.parseExternalUri(getIntent().getData());
if (!stickerParams.isPresent()) {
Log.w(TAG, "Invalid URI!");
@ -156,14 +157,6 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActionBa
stickerAuthor.setText(manifest.getAuthor().or(getString(R.string.StickerPackPreviewActivity_unknown)));
adapter.setStickers(manifest.getStickers());
installButton.setOnClickListener(v -> {
SimpleTask.run(() -> {
ApplicationDependencies.getJobManager().add(new StickerPackDownloadJob(manifest.getPackId(), manifest.getPackKey(), false));
return null;
}, (nothing) -> finish());
});
Sticker first = manifest.getStickers().isEmpty() ? null : manifest.getStickers().get(0);
Sticker cover = manifest.getCover().or(Optional.fromNullable(first)).orNull();

View File

@ -55,7 +55,7 @@ final class StickerPackPreviewViewModel extends ViewModel {
}
void onInstallClicked() {
managementRepository.installStickerPack(packId, packKey);
managementRepository.installStickerPack(packId, packKey, true);
}
void onRemoveClicked() {

View File

@ -20,6 +20,12 @@ public class StickerUrl {
private static final Pattern STICKER_URL_PATTERN = Pattern.compile("^https://signal\\.art/addstickers/#pack_id=(.*)&pack_key=(.*)$");
public static Optional<Pair<String, String>> parseExternalUri(@Nullable Uri uri) {
if (uri == null) return Optional.absent();
return parseActionUri(uri).or(parseShareLink(uri.toString()));
}
public static Optional<Pair<String, String>> parseActionUri(@Nullable Uri uri) {
if (uri == null) return Optional.absent();