Merge pull request #984 from mcginty/sms-prefs

more precise sms controls
This commit is contained in:
Jake McGinty 2014-03-13 21:03:02 -07:00
commit 938545444e
27 changed files with 458 additions and 95 deletions

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#09000000" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_light" />
<corners android:bottomLeftRadius="@dimen/conversation_item_corner_radius" android:bottomRightRadius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#09000000" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_dark" />
<corners android:bottomLeftRadius="@dimen/conversation_item_corner_radius" android:bottomRightRadius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -18,9 +18,10 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="right"
android:gravity="left|center_vertical"
android:layout_marginLeft="6dp"
android:layout_alignParentLeft="true">
android:layout_alignParentLeft="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/sms_failed_indicator"
@ -29,6 +30,15 @@
android:src="@drawable/ic_list_alert_sms_failed"
android:visibility="gone"
android:contentDescription="Send Failed Indicator"/>
<ImageView
android:id="@+id/pending_approval_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_dialog_info_holo_light"
android:visibility="gone"
android:layout_gravity="center_vertical"
android:contentDescription="Pending Approval"/>
</LinearLayout>
<LinearLayout android:id="@+id/conversation_item_parent"
@ -190,5 +200,22 @@
android:scaleType="centerCrop"
android:visibility="gone" />
<TextView android:id="@+id/indicator_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/conversation_item_parent"
android:layout_alignParentRight="true"
android:paddingRight="5dip"
android:paddingLeft="5dip"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="15dp"
android:layout_marginTop="-2dp"
android:textSize="12sp"
android:textColor="?conversation_sent_text_secondary_color"
android:background="?conversation_item_sent_indicator_text_background"
android:visibility="gone" />
</RelativeLayout>
</org.thoughtcrime.securesms.ConversationItem>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingTop="15dp"
android:paddingLeft="15dp"
android:paddingBottom="15dp"
android:paddingRight="10dp">
<CheckBox android:id="@+id/data_users"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:singleLine="true"
android:text="@string/preferences__sms_outgoing_push_users" />
<TextView
style="@style/TextSecureDialogSecondaryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-5dp"
android:layout_marginLeft="32dp"
android:textSize="12sp"
android:text="@string/preferences__sms_outgoing_push_users_description" />
<CheckBox android:id="@+id/ask_before_fallback_data"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/preferences__sms_fallback_ask_fallback"
android:layout_marginLeft="25dp" />
<CheckBox android:id="@+id/non_data_users"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/preferences__sms_fallback_non_push_users" />
</LinearLayout>

View File

@ -4,4 +4,12 @@
<style name="Widget.ProgressBar.Horizontal" parent="@android:style/Widget.Holo.ProgressBar.Horizontal">
</style>
<style name="TextSecureDialogPrimaryText">
<item name="android:textColor">?conversation_list_item_contact_color</item>
</style>
<style name="TextSecureDialogSecondaryText">
<item name="android:textColor">?conversation_list_item_date_color</item>
</style>
</resources>

View File

@ -37,6 +37,7 @@
<attr name="conversation_item_sent_pending_triangle_background" format="reference" />
<attr name="conversation_item_sent_push_background" format="reference" />
<attr name="conversation_item_sent_push_triangle_background" format="reference" />
<attr name="conversation_item_sent_indicator_text_background" format="reference" />
<attr name="dialog_info_icon" format="reference" />
<attr name="dialog_alert_icon" format="reference" />
<attr name="conversation_item_sent_push_pending_background" format="reference" />

View File

@ -32,9 +32,9 @@
<string name="ApplicationPreferencesActivity_you_are_not_registered_with_the_push_service">You are not registered with the push service...</string>
<string name="ApplicationPreferencesActivity_updating_directory">Updating directory</string>
<string name="ApplicationPreferencesActivity_updating_push_directory">Updating push directory...</string>
<string name="ApplicationPreferencesActivity_sms_enabled">SMS Enabled</string>
<string name="ApplicationPreferencesActivity_sms_enabled">Incoming SMS Enabled</string>
<string name="ApplicationPreferencesActivity_touch_to_change_your_default_sms_app">Touch to change your default SMS app</string>
<string name="ApplicationPreferencesActivity_sms_disabled">SMS Disabled</string>
<string name="ApplicationPreferencesActivity_sms_disabled">Incoming SMS Disabled</string>
<string name="ApplicationPreferencesActivity_touch_to_make_textsecure_your_default_sms_app">Touch to make TextSecure your default SMS app</string>
<!-- AttachmentTypeSelectorAdapter -->
@ -63,6 +63,8 @@
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
<string name="ConversationItem_group_action_joined">%1$s have joined the group.</string>
<string name="ConversationItem_group_action_modify">%1$s has updated the group.</string>
<string name="ConversationItem_click_to_approve">Tap for SMS fallback</string>
<string name="ConversationItem_click_to_approve_dialog_title">Fallback to SMS?</string>
<!-- ConversationActivity -->
<string name="ConversationActivity_initiate_secure_session_question">Initiate Secure Session?</string>
@ -642,8 +644,9 @@
<!-- preferences.xml -->
<string name="preferences__general">General</string>
<string name="preferences__pref_all_sms_title">Use for all SMS</string>
<string name="preferences__pref_all_mms_title">Use for all MMS</string>
<string name="preferences__push_sms_category">Push and SMS</string>
<string name="preferences__pref_all_sms_title">Receive all SMS</string>
<string name="preferences__pref_all_mms_title">Receive all MMS</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_text_messages">Use TextSecure for viewing and storing all incoming text messages</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_multimedia_messages">Use TextSecure for viewing and storing all incoming multimedia messages</string>
<string name="preferences__enable_enter_key_title">Enable Enter key</string>
@ -726,11 +729,16 @@
<string name="preferences__use_the_data_channel_for_communication_with_other_textsecure_users">
Increase privacy and avoid SMS fees by using the data channel for communication with other TextSecure users
</string>
<string name="preferences__allow_sms_fallback">SMS Fallback</string>
<string name="preferences__allow_sms_fallback">Allow outgoing SMS to</string>
<string name="preferences__allow_sms_fallback_disabled_reason">TextSecure is currently your default SMS app. Please set another default SMS app first to change this preference.</string>
<string name="preferences__send_and_receive_sms_messages_when_push_is_not_available">Send and receive SMS messages when push is not available</string>
<string name="preferences__refresh_push_directory">Refresh Push Directory</string>
<string name="preferences__submit_debug_log">Submit debug log</string>
<string name="preferences__sms_outgoing_push_users">TextSecure users</string>
<string name="preferences__sms_fallback_push_users_ask">(ask first)</string>
<string name="preferences__sms_outgoing_push_users_description">Send secure SMS if data connectivity is lost</string>
<string name="preferences__sms_fallback_ask_fallback">Ask before sending SMS</string>
<string name="preferences__sms_fallback_non_push_users">Non-TextSecure users</string>
<string name="preferences__sms_fallback_nobody">Nobody</string>
<!-- **************************************** -->
<!-- menus -->

View File

@ -107,4 +107,13 @@
<style name="Widget.ProgressBar.Horizontal" parent="@android:style/Widget.ProgressBar.Horizontal">
</style>
<style name="TextSecureDialogPrimaryText">
<item name="android:textColor">@color/white</item>
</style>
<style name="TextSecureDialogSecondaryText">
<item name="android:textColor">#ff999999</item>
</style>
</resources>

View File

@ -39,6 +39,7 @@
<item name="conversation_item_sent_triangle_background">@drawable/conversation_item_sent_triangle_shape</item>
<item name="conversation_item_sent_push_background">@drawable/conversation_item_sent_push_shape</item>
<item name="conversation_item_sent_push_triangle_background">@drawable/conversation_item_sent_push_triangle_shape</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item>
<item name="dialog_info_icon">@drawable/ic_dialog_info_light</item>
<item name="dialog_alert_icon">@drawable/ic_dialog_alert_light</item>
<item name="conversation_item_sent_pending_background">@drawable/conversation_item_sent_pending_shape</item>
@ -59,7 +60,6 @@
<item name="menu_split_icon">@drawable/ic_menu_split_holo_light</item>
<item name="menu_accept_icon">@drawable/ic_menu_accept_holo_light</item>
<item name="menu_refresh_directory">@drawable/ic_menu_refresh_holo_light</item>
</style>
<style name="TextSecure.LightTheme.NavigationDrawer"
@ -100,6 +100,7 @@
<item name="conversation_item_sent_triangle_background">@drawable/conversation_item_sent_triangle_shape_dark</item>
<item name="conversation_item_sent_push_background">@drawable/conversation_item_sent_push_shape_dark</item>
<item name="conversation_item_sent_push_triangle_background">@drawable/conversation_item_sent_push_triangle_shape_dark</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
<item name="dialog_info_icon">@drawable/ic_dialog_info_dark</item>
<item name="dialog_alert_icon">@drawable/ic_dialog_alert_dark</item>
<item name="conversation_item_sent_pending_background">@drawable/conversation_item_sent_pending_shape_dark</item>

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:key="general_category" android:title="@string/preferences__general">
<PreferenceCategory android:key="push_sms_category" android:title="@string/preferences__push_sms_category">
<CheckBoxPreference android:defaultValue="false"
android:key="pref_toggle_push_messaging"
android:title="@string/preferences__use_data_channel"
android:summary="@string/preferences__use_the_data_channel_for_communication_with_other_textsecure_users"/>
<CheckBoxPreference android:defaultValue="true"
android:key="pref_all_sms"
android:summary="@string/preferences__use_textsecure_for_viewing_and_storing_all_incoming_text_messages"
@ -16,22 +21,14 @@
android:title="@string/preferences__make_default_sms_app"
android:summary="@string/preferences__make_textsecure_the_default_sms_mms_app" />
<CheckBoxPreference android:defaultValue="false"
android:key="pref_toggle_push_messaging"
android:title="@string/preferences__use_data_channel"
android:summary="@string/preferences__use_the_data_channel_for_communication_with_other_textsecure_users"/>
<CheckBoxPreference android:defaultValue="true"
android:enabled="false"
android:key="pref_allow_sms_traffic_out"
android:title="@string/preferences__allow_sms_fallback"
android:summary="@string/preferences__send_and_receive_sms_messages_when_push_is_not_available"/>
<org.thoughtcrime.securesms.components.OutgoingSmsPreference
android:key="pref_outgoing_sms"
android:title="@string/preferences__allow_sms_fallback" />
<CheckBoxPreference android:defaultValue="false"
android:key="pref_delivery_report_sms"
android:summary="@string/preferences__request_a_delivery_report_for_each_sms_message_you_send"
android:title="@string/preferences__sms_delivery_reports" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences__notifications">
<CheckBoxPreference android:key="pref_key_enable_notifications"

View File

@ -47,6 +47,7 @@ import android.widget.Toast;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.gcm.GCMRegistrar;
import org.thoughtcrime.securesms.components.OutgoingSmsPreference;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
@ -77,6 +78,7 @@ import java.io.IOException;
public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static final String TAG = "Preferences";
private static final int PICK_IDENTITY_CONTACT = 1;
private static final int ENABLE_PASSPHRASE_ACTIVITY = 2;
@ -87,6 +89,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
private static final String KITKAT_DEFAULT_PREF = "pref_set_default";
private static final String UPDATE_DIRECTORY_PREF = "pref_update_directory";
private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs";
private static final String OUTGOING_SMS_PREF = "pref_outgoing_sms";
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@ -103,7 +106,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
addPreferencesFromResource(R.xml.preferences);
initializeIdentitySelection();
initializeSmsFallbackOption();
initializePushMessagingToggle();
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF)
@ -126,7 +128,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
.setOnPreferenceClickListener(new DirectoryUpdateListener());
this.findPreference(SUBMIT_DEBUG_LOG_PREF)
.setOnPreferenceClickListener(new SubmitDebugLogListener());
this.findPreference(OUTGOING_SMS_PREF)
.setOnPreferenceChangeListener(new OutgoingSmsPreferenceListener());
initializeOutgoingSmsSummary((OutgoingSmsPreference) findPreference(OUTGOING_SMS_PREF));
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_COLOR_PREF));
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_BLINK_PREF));
initializeRingtoneSummary((RingtonePreference) findPreference(TextSecurePreferences.RINGTONE_PREF));
@ -188,14 +193,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
}
private void initializePlatformSpecificOptions() {
PreferenceGroup generalCategory = (PreferenceGroup) findPreference("general_category");
PreferenceGroup pushSmsCategory = (PreferenceGroup) findPreference("push_sms_category");
Preference defaultPreference = findPreference(KITKAT_DEFAULT_PREF);
Preference allSmsPreference = findPreference(TextSecurePreferences.ALL_SMS_PREF);
Preference allMmsPreference = findPreference(TextSecurePreferences.ALL_MMS_PREF);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (allSmsPreference != null) generalCategory.removePreference(allSmsPreference);
if (allMmsPreference != null) generalCategory.removePreference(allMmsPreference);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && pushSmsCategory != null) {
if (allSmsPreference != null) pushSmsCategory.removePreference(allSmsPreference);
if (allMmsPreference != null) pushSmsCategory.removePreference(allMmsPreference);
if (Util.isDefaultSmsProvider(this)) {
defaultPreference.setIntent(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
@ -208,36 +213,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
defaultPreference.setTitle(getString(R.string.ApplicationPreferencesActivity_sms_disabled));
defaultPreference.setSummary(getString(R.string.ApplicationPreferencesActivity_touch_to_make_textsecure_your_default_sms_app));
}
} else {
if (defaultPreference != null) generalCategory.removePreference(defaultPreference);
}
}
private void initializeSmsFallbackOption() {
CheckBoxPreference allowSmsPreference =
(CheckBoxPreference) findPreference(TextSecurePreferences.ALLOW_SMS_FALLBACK_PREF);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Util.isDefaultSmsProvider(this) || !TextSecurePreferences.isPushRegistered(this)) {
allowSmsPreference.setEnabled(false);
allowSmsPreference.setChecked(true);
allowSmsPreference.setSummary(R.string.preferences__allow_sms_fallback_disabled_reason);
} else {
allowSmsPreference.setEnabled(true);
allowSmsPreference.setSummary(R.string.preferences__send_and_receive_sms_messages_when_push_is_not_available);
}
} else {
if (TextSecurePreferences.isInterceptAllMmsEnabled(this) ||
TextSecurePreferences.isInterceptAllSmsEnabled(this) ||
!TextSecurePreferences.isPushRegistered(this))
{
allowSmsPreference.setEnabled(false);
allowSmsPreference.setChecked(true);
allowSmsPreference.setSummary(R.string.preferences__allow_sms_fallback_disabled_reason);
} else {
allowSmsPreference.setEnabled(true);
allowSmsPreference.setSummary(R.string.preferences__send_and_receive_sms_messages_when_push_is_not_available);
}
} else if (pushSmsCategory != null && defaultPreference != null) {
pushSmsCategory.removePreference(defaultPreference);
}
}
@ -296,6 +273,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
listener.onPreferenceChange(pref, sharedPreferences.getString(pref.getKey(), ""));
}
private void initializeOutgoingSmsSummary(OutgoingSmsPreference pref) {
pref.setSummary(buildOutgoingSmsDescription());
}
private void handleIdentitySelection(Intent data) {
Uri contactUri = data.getData();
@ -311,11 +292,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
dynamicTheme.onResume(this);
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
dynamicLanguage.onResume(this);
} else if (key.equals(TextSecurePreferences.ALL_MMS_PREF) ||
key.equals(TextSecurePreferences.ALL_SMS_PREF) ||
key.equals(TextSecurePreferences.REGISTERED_GCM_PREF))
{
initializeSmsFallbackOption();
}
}
@ -614,6 +590,36 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
}
}
private class OutgoingSmsPreferenceListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
preference.setSummary(buildOutgoingSmsDescription());
return false;
}
}
private String buildOutgoingSmsDescription() {
final StringBuilder builder = new StringBuilder();
final boolean dataFallback = TextSecurePreferences.isSmsFallbackEnabled(this);
final boolean dataFallbackAsk = TextSecurePreferences.isSmsFallbackAskEnabled(this);
final boolean nonData = TextSecurePreferences.isSmsNonDataOutEnabled(this);
if (dataFallback) {
builder.append(getString(R.string.preferences__sms_outgoing_push_users));
if (dataFallbackAsk) builder.append(" ").append(getString(R.string.preferences__sms_fallback_push_users_ask));
}
if (nonData) {
if (dataFallback) builder.append(", ");
builder.append(getString(R.string.preferences__sms_fallback_non_push_users));
}
if (!dataFallback && !nonData) {
builder.append(getString(R.string.preferences__sms_fallback_nobody));
}
return builder.toString();
}
/* http://code.google.com/p/android/issues/detail?id=4611#c35 */
@SuppressWarnings("deprecation")
@Override

View File

@ -44,7 +44,9 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
@ -107,12 +109,15 @@ public class ConversationItem extends LinearLayout {
private View conversationParent;
private TextView bodyText;
private TextView dateText;
private TextView indicatorText;
private TextView groupStatusText;
private ImageView secureImage;
private ImageView failedImage;
private ImageView keyImage;
private ImageView contactPhoto;
private ImageView deliveredImage;
private View triangleTick;
private ImageView pendingIndicator;
private View mmsContainer;
private ImageView mmsThumbnail;
@ -144,6 +149,7 @@ public class ConversationItem extends LinearLayout {
this.bodyText = (TextView) findViewById(R.id.conversation_item_body);
this.dateText = (TextView) findViewById(R.id.conversation_item_date);
this.indicatorText = (TextView) findViewById(R.id.indicator_text);
this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator);
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
@ -155,6 +161,8 @@ public class ConversationItem extends LinearLayout {
this.contactPhoto = (ImageView)findViewById(R.id.contact_photo);
this.deliveredImage = (ImageView)findViewById(R.id.delivered_indicator);
this.conversationParent = (View) findViewById(R.id.conversation_item_parent);
this.triangleTick = findViewById(R.id.triangle_tick);
this.pendingIndicator = (ImageView)findViewById(R.id.pending_approval_indicator);
this.backgroundDrawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
setOnClickListener(clickListener);
@ -180,6 +188,7 @@ public class ConversationItem extends LinearLayout {
setContactPhoto(messageRecord);
setGroupMessageStatus(messageRecord);
setEvents(messageRecord);
setMinimumWidth();
if (messageRecord instanceof NotificationMmsMessageRecord) {
setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord);
@ -218,10 +227,10 @@ public class ConversationItem extends LinearLayout {
if (messageRecord.isOutgoing()) {
final int background;
final int triangleBackground;
if (messageRecord.isPending() && pushDestination) {
if (messageRecord.isPending() && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
} else if (messageRecord.isPending()) {
} else if (messageRecord.isPending() || messageRecord.isPendingFallbackApproval()) {
background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) {
@ -232,7 +241,7 @@ public class ConversationItem extends LinearLayout {
triangleBackground = SENT_SMS_TRIANGLE;
}
setViewBackgroundWithoutResettingPadding(conversationParent, backgroundDrawables.getResourceId(background, -1));
setViewBackgroundWithoutResettingPadding(findViewById(R.id.triangle_tick), backgroundDrawables.getResourceId(triangleBackground, -1));
setViewBackgroundWithoutResettingPadding(triangleTick, backgroundDrawables.getResourceId(triangleBackground, -1));
}
}
}
@ -255,6 +264,10 @@ public class ConversationItem extends LinearLayout {
private void setStatusIcons(MessageRecord messageRecord) {
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
if (messageRecord.isOutgoing()) {
pendingIndicator.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE);
indicatorText.setVisibility(messageRecord.isPendingFallbackApproval() ? View.VISIBLE : View.GONE);
}
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE);
deliveredImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
@ -265,6 +278,9 @@ public class ConversationItem extends LinearLayout {
if (messageRecord.isFailed()) {
dateText.setText(R.string.ConversationItem_error_sending_message);
} else if (messageRecord.isPendingFallbackApproval() && indicatorText != null) {
dateText.setText("");
indicatorText.setText(R.string.ConversationItem_click_to_approve);
} else if (messageRecord.isPending()) {
dateText.setText(" ··· ");
} else {
@ -276,10 +292,19 @@ public class ConversationItem extends LinearLayout {
}
}
private void setMinimumWidth() {
if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) {
conversationParent.setMinimumWidth(indicatorText.getText().length() * 20);
} else {
conversationParent.setMinimumWidth(0);
}
}
private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isKeyExchange() &&
!messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing());
setClickable(messageRecord.isPendingFallbackApproval() ||
(messageRecord.isKeyExchange() &&
!messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing()));
if (!messageRecord.isOutgoing() &&
messageRecord.getRecipients().isSingleRecipient() &&
@ -615,9 +640,42 @@ public class ConversationItem extends LinearLayout {
!messageRecord.isProcessedKeyExchange() &&
!messageRecord.isStaleKeyExchange())
handleKeyExchangeClicked();
else if (messageRecord.isPendingFallbackApproval())
handleMessageApproval();
}
}
private void handleMessageApproval() {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationItem_click_to_approve_dialog_title);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());
} else {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());
}
Intent intent = new Intent(context, SendReceiveService.class);
intent.setAction(SendReceiveService.SEND_SMS_ACTION);
intent.putExtra(SendReceiveService.MASTER_SECRET_EXTRA, masterSecret);
context.startService(intent);
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId());
else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId());
}
});
builder.show();
}
private void handleAbortSecureSession() {
if (!messageRecord.isSecure()) return;

View File

@ -159,7 +159,7 @@ public class PushContactSelectionListFragment extends SherlockListFragment
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ContactSelectionlistFragment_select_for + " " + contactData.name);
builder.setTitle(getString(R.string.ContactSelectionlistFragment_select_for) + " " + contactData.name);
builder.setMultiChoiceItems(options, null, new DiscriminatorClickedListener(contactData));
builder.setPositiveButton(android.R.string.ok, new DiscriminatorFinishedListener(contactData, textView, checkBox));
builder.setOnCancelListener(new DiscriminatorFinishedListener(contactData, textView, checkBox));

View File

@ -0,0 +1,54 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class OutgoingSmsPreference extends DialogPreference {
private CheckBox dataUsers;
private CheckBox askForFallback;
private CheckBox nonDataUsers;
public OutgoingSmsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setPersistent(false);
setDialogLayoutResource(R.layout.outgoing_sms_preference);
}
@Override
protected void onBindDialogView(final View view) {
super.onBindDialogView(view);
dataUsers = (CheckBox) view.findViewById(R.id.data_users);
askForFallback = (CheckBox) view.findViewById(R.id.ask_before_fallback_data);
nonDataUsers = (CheckBox) view.findViewById(R.id.non_data_users);
dataUsers.setChecked(TextSecurePreferences.isSmsFallbackEnabled(getContext()));
askForFallback.setChecked(TextSecurePreferences.isSmsFallbackAskEnabled(getContext()));
nonDataUsers.setChecked(TextSecurePreferences.isSmsNonDataOutEnabled(getContext()));
dataUsers.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
askForFallback.setEnabled(dataUsers.isChecked());
}
});
askForFallback.setEnabled(dataUsers.isChecked());
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
TextSecurePreferences.setSmsFallbackEnabled(getContext(), dataUsers.isChecked());
TextSecurePreferences.setSmsFallbackAskEnabled(getContext(), askForFallback.isChecked());
TextSecurePreferences.setSmsNonDataOutEnabled(getContext(), nonDataUsers.isChecked());
if (getOnPreferenceChangeListener() != null) getOnPreferenceChangeListener().onPreferenceChange(this, null);
}
}
}

View File

@ -281,6 +281,15 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsForcedSms(long id) {
updateMailboxBitmask(id, 0, Types.MESSAGE_FORCE_SMS_BIT);
}
public void markAsPendingApproval(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL);
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsSending(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
notifyConversationListeners(getThreadIdForMessage(messageId));

View File

@ -15,16 +15,22 @@ public interface MmsSmsColumns {
protected static final long TOTAL_MASK = 0xFFFFFFFF;
// Base Types
protected static final long BASE_TYPE_MASK = 0xFF;
protected static final long BASE_TYPE_MASK = 0x1F;
protected static final long BASE_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21;
protected static final long BASE_SENDING_TYPE = 22;
protected static final long BASE_SENT_TYPE = 23;
protected static final long BASE_SENT_FAILED_TYPE = 24;
protected static final long BASE_INBOX_TYPE = 20;
protected static final long BASE_OUTBOX_TYPE = 21;
protected static final long BASE_SENDING_TYPE = 22;
protected static final long BASE_SENT_TYPE = 23;
protected static final long BASE_SENT_FAILED_TYPE = 24;
protected static final long BASE_PENDING_FALLBACK_APPROVAL = 25;
protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE};
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
BASE_PENDING_FALLBACK_APPROVAL};
// Message attributes
protected static final long MESSAGE_ATTRIBUTE_MASK = 0xE0;
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
// Key Exchange Information
protected static final long KEY_EXCHANGE_BIT = 0x8000;
@ -65,12 +71,20 @@ public interface MmsSmsColumns {
return false;
}
public static boolean isForcedSms(long type) {
return (type & MESSAGE_FORCE_SMS_BIT) != 0;
}
public static boolean isPendingMessageType(long type) {
return
(type & BASE_TYPE_MASK) == BASE_OUTBOX_TYPE ||
(type & BASE_TYPE_MASK) == BASE_SENDING_TYPE;
}
public static boolean isPendingApprovalType(long type) {
return (type & BASE_TYPE_MASK) == BASE_PENDING_FALLBACK_APPROVAL;
}
public static boolean isInboxType(long type) {
return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE;
}

View File

@ -178,6 +178,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, 0, Types.PUSH_MESSAGE_BIT);
}
public void markAsForcedSms(long id) {
updateTypeBitmask(id, 0, Types.MESSAGE_FORCE_SMS_BIT);
}
public void markAsDecryptFailed(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT);
}
@ -194,6 +198,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
}
public void markAsPendingApproval(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_FALLBACK_APPROVAL);
}
public void markAsSending(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
}

View File

@ -113,6 +113,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isPushType(type);
}
public boolean isForcedSms() {
return SmsDatabase.Types.isForcedSms(type);
}
public boolean isStaleKeyExchange() {
return SmsDatabase.Types.isStaleKeyExchange(type);
}
@ -121,6 +125,10 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isProcessedKeyExchange(type);
}
public boolean isPendingFallbackApproval() {
return SmsDatabase.Types.isPendingApprovalType(type);
}
public boolean isBundleKeyExchange() {
return SmsDatabase.Types.isBundleKeyExchange(type);
}

View File

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.thoughtcrime.securesms.transport.UserInterventionRequiredException;
import org.whispersystems.textsecure.crypto.MasterSecret;
import ws.com.google.android.mms.MmsException;
@ -83,6 +84,11 @@ public class MmsSender {
result.getResponseStatus());
systemStateListener.unregisterForConnectivityChange();
} catch (UserInterventionRequiredException uire) {
Log.w("MmsSender", uire);
database.markAsPendingApproval(message.getDatabaseMessageId());
Recipients recipients = threads.getRecipientsForThreadId(threadId);
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} catch (UndeliverableMessageException e) {
Log.w("MmsSender", e);
database.markAsSentFailed(message.getDatabaseMessageId());

View File

@ -62,6 +62,8 @@ public class SendReceiveService extends Service {
public static final String DOWNLOAD_PUSH_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_PUSH_ACTION";
public static final String DOWNLOAD_AVATAR_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DOWNLOAD_AVATAR_ACTION";
public static final String MASTER_SECRET_EXTRA = "master_secret";
private static final int SEND_SMS = 0;
private static final int RECEIVE_SMS = 1;
private static final int SEND_MMS = 2;
@ -307,7 +309,7 @@ public class SendReceiveService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
Log.w("SendReceiveService", "Got a MasterSecret broadcast...");
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra(MASTER_SECRET_EXTRA));
}
}

View File

@ -40,6 +40,8 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.transport.UniversalTransport;
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
import org.thoughtcrime.securesms.transport.UserInterventionRequiredException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.storage.Session;
@ -84,6 +86,10 @@ public class SmsSender {
database.markAsSending(record.getId());
transport.deliver(record);
} catch (UserInterventionRequiredException uire) {
Log.w("SmsSender", uire);
DatabaseFactory.getSmsDatabase(context).markAsPendingApproval(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (UntrustedIdentityException e) {
Log.w("SmsSender", e);
IncomingIdentityUpdateMessage identityUpdateMessage = IncomingIdentityUpdateMessage.createFor(e.getE164Number(), e.getIdentityKey());
@ -92,6 +98,7 @@ public class SmsSender {
} catch (UndeliverableMessageException ude) {
Log.w("SmsSender", ude);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (RetryLaterException rle) {
Log.w("SmsSender", rle);
DatabaseFactory.getSmsDatabase(context).markAsOutbox(record.getId());

View File

@ -48,9 +48,7 @@ public class SmsTransport extends BaseTransport {
}
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
if (TextSecurePreferences.isPushRegistered(context) &&
!TextSecurePreferences.isSmsFallbackEnabled(context))
{
if (!TextSecurePreferences.isSmsNonDataOutEnabled(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) {
throw new UndeliverableMessageException("SMS Transport is not enabled!");
}

View File

@ -60,7 +60,7 @@ public class UniversalTransport {
}
public void deliver(SmsMessageRecord message)
throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException
throws UndeliverableMessageException, UntrustedIdentityException, RetryLaterException, UserInterventionRequiredException
{
if (!TextSecurePreferences.isPushRegistered(context)) {
smsTransport.deliver(message);
@ -71,23 +71,25 @@ public class UniversalTransport {
Recipient recipient = message.getIndividualRecipient();
String number = Util.canonicalizeNumber(context, recipient.getNumber());
if (isPushTransport(number) && !message.isKeyExchange()) {
if (isPushTransport(number) && !message.isKeyExchange() && !message.isForcedSms()) {
boolean isSmsFallbackSupported = isSmsFallbackSupported(number);
try {
Log.w("UniversalTransport", "Delivering with GCM...");
Log.w("UniversalTransport", "Using GCM as transport...");
pushTransport.deliver(message);
} catch (UnregisteredUserException uue) {
Log.w("UnviersalTransport", uue);
if (isSmsFallbackSupported) smsTransport.deliver(message);
Log.w("UniversalTransport", uue);
if (isSmsFallbackSupported) fallbackOrAskApproval(message, number);
else throw new UndeliverableMessageException(uue);
} catch (IOException ioe) {
Log.w("UniversalTransport", ioe);
if (isSmsFallbackSupported) smsTransport.deliver(message);
if (isSmsFallbackSupported) fallbackOrAskApproval(message, number);
else throw new RetryLaterException(ioe);
}
} else if (!message.isForcedSms() && !TextSecurePreferences.isSmsNonDataOutEnabled(context)) {
throw new UndeliverableMessageException("User disallows non-push outgoing SMS");
} else {
Log.w("UniversalTransport", "Delivering with SMS...");
Log.w("UniversalTransport", "Using SMS as transport...");
smsTransport.deliver(message);
}
} catch (InvalidNumberException e) {
@ -97,7 +99,7 @@ public class UniversalTransport {
}
public MmsSendResult deliver(SendReq mediaMessage, long threadId)
throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException
throws UndeliverableMessageException, RetryLaterException, UntrustedIdentityException, UserInterventionRequiredException
{
if (Util.isEmpty(mediaMessage.getTo())) {
return mmsTransport.deliver(mediaMessage);
@ -122,16 +124,16 @@ public class UniversalTransport {
boolean isSmsFallbackSupported = isSmsFallbackSupported(destination);
try {
Log.w("UniversalTransport", "Delivering media message with GCM...");
Log.w("UniversalTransport", "Using GCM as transport...");
pushTransport.deliver(mediaMessage, threadId);
return new MmsSendResult("push".getBytes("UTF-8"), 0, true, true);
} catch (IOException ioe) {
Log.w("UniversalTransport", ioe);
if (isSmsFallbackSupported) return mmsTransport.deliver(mediaMessage);
if (isSmsFallbackSupported) return fallbackOrAskApproval(mediaMessage, destination);
else throw new RetryLaterException(ioe);
} catch (RecipientFormattingException e) {
Log.w("UniversalTransport", e);
if (isSmsFallbackSupported) return mmsTransport.deliver(mediaMessage);
if (isSmsFallbackSupported) return fallbackOrAskApproval(mediaMessage, destination);
else throw new UndeliverableMessageException(e);
} catch (EncapsulatedExceptions ee) {
Log.w("UniversalTransport", ee);
@ -152,6 +154,32 @@ public class UniversalTransport {
}
}
private MmsSendResult fallbackOrAskApproval(SendReq mediaMessage, String destination)
throws UserInterventionRequiredException, UndeliverableMessageException
{
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Falling back to MMS without user intervention");
return mmsTransport.deliver(mediaMessage);
} else {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS");
}
}
private void fallbackOrAskApproval(SmsMessageRecord smsMessage, String destination)
throws UserInterventionRequiredException, UndeliverableMessageException
{
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination);
if (!isSmsFallbackApprovalRequired) {
Log.i("UniversalTransport", "Falling back to SMS without user intervention");
smsTransport.deliver(smsMessage);
} else {
Log.i("UniversalTransport", "Marking message as pending user approval per their settings");
throw new UserInterventionRequiredException("Pending user approval for fallback to SMS");
}
}
private MmsSendResult deliverGroupMessage(SendReq mediaMessage, long threadId)
throws RetryLaterException, UndeliverableMessageException
{
@ -209,6 +237,10 @@ public class UniversalTransport {
return recipientCount > 1;
}
private boolean isSmsFallbackApprovalRequired(String destination) {
return (isSmsFallbackSupported(destination) && TextSecurePreferences.isSmsFallbackAskEnabled(context));
}
private boolean isSmsFallbackSupported(String destination) {
if (GroupUtil.isEncodedGroup(destination)) {
return false;

View File

@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.transport;
public class UserInterventionRequiredException extends Exception {
public UserInterventionRequiredException(String detailMessage) {
super(detailMessage);
}
}

View File

@ -10,8 +10,8 @@ import org.thoughtcrime.securesms.R;
public class ActionBarUtil {
public static void initializeDefaultActionBar(final Context c, final ActionBar actionBar, final int title_resid) {
actionBar.setTitle(title_resid);
public static void initializeDefaultActionBar(final Context c, final ActionBar actionBar, final int titleResId) {
actionBar.setTitle(titleResId);
initializeDefaultActionBar(c, actionBar);
}

View File

@ -45,12 +45,35 @@ public class TextSecurePreferences {
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id";
public static final String ALLOW_SMS_FALLBACK_PREF = "pref_allow_sms_traffic_out";
private static final String ALLOW_SMS_FALLBACK_PREF = "pref_allow_sms_traffic_out";
private static final String SMS_FALLBACK_ASK_PREF = "pref_sms_fallback_ask";
private static final String ALLOW_SMS_NON_DATA_PREF = "pref_sms_non_data_out";
public static boolean isSmsFallbackEnabled(Context context) {
return getBooleanPreference(context, ALLOW_SMS_FALLBACK_PREF, true);
}
public static void setSmsFallbackEnabled(Context context, boolean enabled) {
setBooleanPreference(context, ALLOW_SMS_FALLBACK_PREF, enabled);
}
public static boolean isSmsNonDataOutEnabled(Context context) {
return getBooleanPreference(context, ALLOW_SMS_NON_DATA_PREF, true);
}
public static void setSmsNonDataOutEnabled(Context context, boolean enabled) {
setBooleanPreference(context, ALLOW_SMS_NON_DATA_PREF, enabled);
}
public static boolean isSmsFallbackAskEnabled(Context context) {
return getBooleanPreference(context, SMS_FALLBACK_ASK_PREF, false);
}
public static void setSmsFallbackAskEnabled(Context context, boolean enabled) {
setBooleanPreference(context, SMS_FALLBACK_ASK_PREF, enabled);
}
public static int getLocalRegistrationId(Context context) {
return getIntegerPreference(context, LOCAL_REGISTRATION_ID_PREF, 0);
}