Remove old profile sharing UI.

fork-5.53.8
Greyson Parrelli 2020-11-12 12:01:43 -05:00 zatwierdzone przez GitHub
rodzic 3fc4b098e8
commit 3b2a5f1ce3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
14 zmienionych plików z 7 dodań i 372 usunięć

Wyświetl plik

@ -212,7 +212,6 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment;
import org.thoughtcrime.securesms.providers.BlobProvider;
@ -353,7 +352,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
private InputAwareLayout container;
protected Stub<ReminderView> reminderView;
private Stub<UnverifiedBannerView> unverifiedBannerView;
private Stub<GroupShareProfileView> groupShareProfileView;
private Stub<ReviewBannerView> reviewBanner;
private TypingStatusTextWatcher typingTextWatcher;
private ConversationSearchBottomBar searchNav;
@ -1868,7 +1866,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
container = ViewUtil.findById(this, R.id.layout_container);
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
groupShareProfileView = ViewUtil.findStubById(this, R.id.group_share_profile_view_stub);
reviewBanner = ViewUtil.findStubById(this, R.id.review_banner_stub);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
@ -3422,22 +3419,9 @@ public class ConversationActivity extends PassphraseRequiredActivity
switch (displayState) {
case DISPLAY_MESSAGE_REQUEST:
messageRequestBottomView.setVisibility(View.VISIBLE);
if (groupShareProfileView.resolved()) {
groupShareProfileView.get().setVisibility(View.GONE);
}
break;
case DISPLAY_PRE_MESSAGE_REQUEST:
if (recipient.get().isGroup()) {
groupShareProfileView.get().setRecipient(recipient.get());
groupShareProfileView.get().setVisibility(View.VISIBLE);
}
messageRequestBottomView.setVisibility(View.GONE);
break;
case DISPLAY_NONE:
messageRequestBottomView.setVisibility(View.GONE);
if (groupShareProfileView.resolved()) {
groupShareProfileView.get().setVisibility(View.GONE);
}
break;
}
}

Wyświetl plik

@ -10,7 +10,6 @@ final class ConversationData {
private final int lastScrolledPosition;
private final boolean hasSent;
private final boolean isMessageRequestAccepted;
private final boolean hasPreMessageRequestMessages;
private final int jumpToPosition;
private final int threadSize;
@ -20,7 +19,6 @@ final class ConversationData {
int lastScrolledPosition,
boolean hasSent,
boolean isMessageRequestAccepted,
boolean hasPreMessageRequestMessages,
int jumpToPosition,
int threadSize)
{
@ -30,7 +28,6 @@ final class ConversationData {
this.lastScrolledPosition = lastScrolledPosition;
this.hasSent = hasSent;
this.isMessageRequestAccepted = isMessageRequestAccepted;
this.hasPreMessageRequestMessages = hasPreMessageRequestMessages;
this.jumpToPosition = jumpToPosition;
this.threadSize = threadSize;
}
@ -59,10 +56,6 @@ final class ConversationData {
return isMessageRequestAccepted;
}
boolean hasPreMessageRequestMessages() {
return hasPreMessageRequestMessages;
}
boolean shouldJumpToMessage() {
return jumpToPosition >= 0;
}

Wyświetl plik

@ -84,7 +84,6 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationM
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -107,7 +106,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
@ -171,7 +169,6 @@ public class ConversationFragment extends LoggingFragment {
private ViewSwitcher topLoadMoreView;
private ViewSwitcher bottomLoadMoreView;
private ConversationTypingView typingView;
private UnknownSenderView unknownSenderView;
private View composeDivider;
private ConversationScrollToView scrollToBottomButton;
private ConversationScrollToView scrollToMentionButton;
@ -483,7 +480,6 @@ public class ConversationFragment extends LoggingFragment {
this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA));
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId, () -> clearHeaderIfNotTyping(getListAdapter()));
this.markReadHelper = new MarkReadHelper(threadId, requireContext());
conversationViewModel.onConversationDataAvailable(threadId, startingPosition);
@ -951,15 +947,7 @@ public class ConversationFragment extends LoggingFragment {
setLastSeen(conversation.getLastSeen());
if (!conversation.hasPreMessageRequestMessages()) {
clearHeaderIfNotTyping(adapter);
} else {
if (!conversation.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
adapter.setHeaderView(unknownSenderView);
} else {
clearHeaderIfNotTyping(adapter);
}
}
clearHeaderIfNotTyping(adapter);
listener.onCursorChanged();

Wyświetl plik

@ -44,8 +44,7 @@ class ConversationRepository {
long lastScrolled = metadata.getLastScrolled();
int lastScrolledPosition = 0;
boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);
boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
if (lastSeen > 0) {
lastSeenPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionOnOrAfterTimestamp(threadId, lastSeen);
@ -59,6 +58,6 @@ class ConversationRepository {
lastScrolledPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionOnOrAfterTimestamp(threadId, lastScrolled);
}
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages, jumpToPosition, threadSize);
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, hasSent, isMessageRequestAccepted, jumpToPosition, threadSize);
}
}

Wyświetl plik

@ -99,10 +99,8 @@ final class MessageRequestRepository {
}
return MessageRequestState.REQUIRED;
} else if (FeatureFlags.modernProfileSharing() && !RecipientUtil.isLegacyProfileSharingAccepted(recipient) && threadId > 0) {
} else if (!RecipientUtil.isLegacyProfileSharingAccepted(recipient) && threadId > 0) {
return MessageRequestState.REQUIRED;
} else if (RecipientUtil.isPreMessageRequestThread(context, threadId) && !RecipientUtil.isLegacyProfileSharingAccepted(recipient)) {
return MessageRequestState.PRE_MESSAGE_REQUEST;
} else {
return MessageRequestState.NOT_REQUIRED;
}
@ -261,9 +259,6 @@ final class MessageRequestRepository {
NOT_REQUIRED,
/** Explicit message request permission is required. */
REQUIRED,
/** This conversation existed before message requests and needs the old UI */
PRE_MESSAGE_REQUEST
REQUIRED
}
}

Wyświetl plik

@ -225,9 +225,6 @@ public class MessageRequestViewModel extends ViewModel {
case REQUIRED:
displayState.postValue(DisplayState.DISPLAY_MESSAGE_REQUEST);
break;
case PRE_MESSAGE_REQUEST:
displayState.postValue(DisplayState.DISPLAY_PRE_MESSAGE_REQUEST);
break;
}
});
}
@ -237,8 +234,7 @@ public class MessageRequestViewModel extends ViewModel {
Context context = ApplicationDependencies.getApplication();
Long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient.getId());
return FeatureFlags.modernProfileSharing() &&
threadId != null &&
return threadId != null &&
(RecipientUtil.hasSentMessageInThread(context, threadId) || RecipientUtil.isPreMessageRequestThread(context, threadId));
}
@ -283,7 +279,7 @@ public class MessageRequestViewModel extends ViewModel {
}
public enum DisplayState {
DISPLAY_MESSAGE_REQUEST, DISPLAY_PRE_MESSAGE_REQUEST, DISPLAY_NONE
DISPLAY_MESSAGE_REQUEST, DISPLAY_NONE
}
public enum MessageClass {

Wyświetl plik

@ -1,69 +0,0 @@
package org.thoughtcrime.securesms.profiles;
import android.content.Context;
import android.os.Build;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ViewUtil;
public class GroupShareProfileView extends FrameLayout {
private View container;
private @Nullable Recipient recipient;
public GroupShareProfileView(@NonNull Context context) {
super(context);
initialize();
}
public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public GroupShareProfileView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.profile_group_share_view, this);
this.container = ViewUtil.findById(this, R.id.container);
this.container.setOnClickListener(view -> {
if (this.recipient != null) {
new AlertDialog.Builder(getContext())
.setIcon(R.drawable.ic_info_outline)
.setTitle(R.string.GroupShareProfileView_share_your_profile_name_and_photo_with_this_group)
.setMessage(R.string.GroupShareProfileView_do_you_want_to_make_your_profile_name_and_photo_visible_to_all_current_and_future_members_of_this_group)
.setPositiveButton(R.string.GroupShareProfileView_make_visible, (dialog, which) -> {
DatabaseFactory.getRecipientDatabase(getContext()).setProfileSharing(recipient.getId(), true);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
});
}
public void setRecipient(@NonNull Recipient recipient) {
this.recipient = recipient;
}
}

Wyświetl plik

@ -1,102 +0,0 @@
package org.thoughtcrime.securesms.profiles;
import android.content.Context;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.view.View;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
public class UnknownSenderView extends FrameLayout {
private final @NonNull Recipient recipient;
private final long threadId;
private final Listener listener;
public UnknownSenderView(@NonNull Context context, @NonNull Recipient recipient, long threadId, @NonNull Listener listener) {
super(context);
this.recipient = recipient;
this.threadId = threadId;
this.listener = listener;
inflate(context, R.layout.unknown_sender_view, this);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
View block = ViewUtil.findById(this, R.id.block);
View add = ViewUtil.findById(this, R.id.add_to_contacts);
View profileAccess = ViewUtil.findById(this, R.id.share_profile);
block.setOnClickListener(v -> handleBlock());
add.setOnClickListener(v -> handleAdd());
profileAccess.setOnClickListener(v -> handleProfileAccess());
}
private void handleBlock() {
final Context context = getContext();
new AlertDialog.Builder(getContext())
.setIcon(R.drawable.ic_warning)
.setTitle(getContext().getString(R.string.UnknownSenderView_block_s, recipient.getDisplayName(context)))
.setMessage(R.string.UnknownSenderView_blocked_contacts_will_no_longer_be_able_to_send_you_messages_or_call_you)
.setPositiveButton(R.string.UnknownSenderView_block, (dialog, which) -> {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(context).setBlocked(recipient.getId(), true);
if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
listener.onActionTaken();
}
}.executeOnExecutor(SignalExecutors.BOUNDED);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void handleAdd() {
getContext().startActivity(RecipientExporter.export(recipient).asAddContactIntent());
if (threadId != -1) DatabaseFactory.getThreadDatabase(getContext()).setHasSent(threadId, true);
listener.onActionTaken();
}
private void handleProfileAccess() {
final Context context = getContext();
new AlertDialog.Builder(getContext())
.setIcon(R.drawable.ic_info_outline)
.setTitle(getContext().getString(R.string.UnknownSenderView_share_profile_with_s, recipient.getDisplayName(context)))
.setMessage(R.string.UnknownSenderView_the_easiest_way_to_share_your_profile_information_is_to_add_the_sender_to_your_contacts)
.setPositiveButton(R.string.UnknownSenderView_share_profile, (dialog, which) -> {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient.getId(), true);
if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
listener.onActionTaken();
}
}.executeOnExecutor(SignalExecutors.BOUNDED);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
public interface Listener {
void onActionTaken();
}
}

Wyświetl plik

@ -59,7 +59,6 @@ public final class FeatureFlags {
private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion";
private static final String CLIENT_EXPIRATION = "android.clientExpiration";
public static final String RESEARCH_MEGAPHONE_1 = "research.megaphone.1";
public static final String MODERN_PROFILE_SHARING = "android.modernProfileSharing";
private static final String VIEWED_RECEIPTS = "android.viewed.receipts";
private static final String MAX_ENVELOPE_SIZE = "android.maxEnvelopeSize";
private static final String GV1_AUTO_MIGRATE_VERSION = "android.groupsv2.autoMigrateVersion";
@ -82,7 +81,6 @@ public final class FeatureFlags {
VERIFY_V2,
CLIENT_EXPIRATION,
RESEARCH_MEGAPHONE_1,
MODERN_PROFILE_SHARING,
VIEWED_RECEIPTS,
MAX_ENVELOPE_SIZE,
GV1_AUTO_MIGRATE_VERSION,
@ -252,11 +250,6 @@ public final class FeatureFlags {
return getVersionFlag(PHONE_NUMBER_PRIVACY_VERSION) == VersionFlag.ON;
}
/** Whether or not to show the new profile sharing prompt for legacy conversations. */
public static boolean modernProfileSharing() {
return getBoolean(MODERN_PROFILE_SHARING, false);
}
/** Whether the user should display the content revealed dot in voice notes. */
public static boolean viewedReceipts() {
return getBoolean(VIEWED_RECEIPTS, false);

Wyświetl plik

@ -49,13 +49,6 @@
android:inflatedId="@+id/review_banner"
android:layout="@layout/review_banner_view" />
<ViewStub
android:id="@+id/group_share_profile_view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/group_share_profile_view"
android:layout="@layout/conversation_activity_group_share_profile_stub" />
<ViewStub
android:id="@+id/unverified_banner_stub"
android:layout_width="match_parent"

Wyświetl plik

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.profiles.GroupShareProfileView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/group_share_profile_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

Wyświetl plik

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/reminder_background_normal"
android:focusable="true"
android:orientation="horizontal"
tools:visibility="visible">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_face_white_24dp"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="18sp"
android:layout_margin="16dp"
android:text="@string/profile_group_share_view__make_your_profile_name_and_photo_visible_to_this_group"/>
</LinearLayout>

Wyświetl plik

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_marginTop="20dp"
android:background="@color/signal_background_primary">
<TextView android:id="@+id/unknown_sender_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="25dp"
android:textColor="?android:textColorSecondary"
android:text="@string/unknown_sender_view__the_sender_is_not_in_your_contact_list"/>
<LinearLayout android:id="@+id/block"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:clickable="true"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_block_white_24dp"
android:tint="@color/core_ultramarine"/>
<TextView android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__block"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/core_ultramarine"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout android:id="@+id/add_to_contacts"
android:clickable="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_person_add_white_24dp"
android:tint="@color/core_ultramarine"/>
<TextView android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__add_to_contacts"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/core_ultramarine"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout android:id="@+id/share_profile"
android:clickable="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_face_white_24dp"
android:tint="@color/core_ultramarine"/>
<TextView android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__don_t_add_but_make_my_profile_visible"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/core_ultramarine"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>

Wyświetl plik

@ -1562,14 +1562,6 @@
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
<string name="UpdateApkReadyListener_a_new_version_of_signal_is_available_tap_to_update">A new version of Signal is available, tap to update</string>
<!-- UnknownSenderView -->
<string name="UnknownSenderView_block_s">Block %s?</string>
<string name="UnknownSenderView_blocked_contacts_will_no_longer_be_able_to_send_you_messages_or_call_you">Blocked contacts will no longer be able to send you messages or call you.</string>
<string name="UnknownSenderView_block">Block</string>
<string name="UnknownSenderView_share_profile_with_s">Share profile with %s?</string>
<string name="UnknownSenderView_the_easiest_way_to_share_your_profile_information_is_to_add_the_sender_to_your_contacts">The easiest way to share your profile information is to add the sender to your contacts. If you do not wish to, you can still share your profile information this way.</string>
<string name="UnknownSenderView_share_profile">Share profile</string>
<!-- UntrustedSendDialog -->
<string name="UntrustedSendDialog_send_message">Send message?</string>
<string name="UntrustedSendDialog_send">Send</string>
@ -2034,9 +2026,6 @@
</plurals>
<string name="GroupUtil_group_name_is_now">Group name is now \'%1$s\'.</string>
<!-- profile_group_share_view -->
<string name="profile_group_share_view__make_your_profile_name_and_photo_visible_to_this_group">Make your profile name and photo visible to this group?</string>
<!-- prompt_passphrase_activity -->
<string name="prompt_passphrase_activity__unlock">Unlock</string>