mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
As of 'N' we can no longer offer external ringtone selection
1. Replace custom ringtone picker with system Intent, since we don't need it anymore. Fixes #7174 2. Make sure 'silent' ringtone selection is stored appropriately Fixes #7115 Closes #7141 3. Replace any existing file:// notification URIs with the system default Fixes #7234
This commit is contained in:
parent
4cb2ac7b27
commit
3523952ef9
@ -9,11 +9,11 @@
|
||||
android:title="@string/preferences__notifications"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<org.thoughtcrime.securesms.preferences.widgets.SignalRingtonePreference
|
||||
<org.thoughtcrime.securesms.preferences.widgets.SignalPreference
|
||||
android:dependency="pref_key_enable_notifications"
|
||||
android:key="pref_key_ringtone"
|
||||
android:title="@string/preferences__sound"
|
||||
android:ringtoneType="notification"
|
||||
android:persistent="false"
|
||||
android:defaultValue="content://settings/system/notification_sound" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
|
@ -10,13 +10,10 @@
|
||||
android:disableDependentsState="true"
|
||||
android:persistent="false" />
|
||||
|
||||
<org.thoughtcrime.securesms.preferences.widgets.SignalRingtonePreference
|
||||
<org.thoughtcrime.securesms.preferences.widgets.SignalPreference
|
||||
android:dependency="pref_key_recipient_mute"
|
||||
android:key="pref_key_recipient_ringtone"
|
||||
android:title="@string/recipient_preferences__notification_sound"
|
||||
android:ringtoneType="notification"
|
||||
android:showSilent="true"
|
||||
android:showDefault="true"
|
||||
android:persistent="false"/>
|
||||
|
||||
<org.thoughtcrime.securesms.preferences.widgets.SignalListPreference
|
||||
|
@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.AdvancedRingtonePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
@ -59,7 +58,6 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
@ -248,6 +246,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
|
||||
this.findPreference(PREFERENCE_TONE)
|
||||
.setOnPreferenceChangeListener(new RingtoneChangeListener());
|
||||
this.findPreference(PREFERENCE_TONE)
|
||||
.setOnPreferenceClickListener(new RingtoneClickedListener());
|
||||
this.findPreference(PREFERENCE_VIBRATE)
|
||||
.setOnPreferenceChangeListener(new VibrateChangeListener());
|
||||
this.findPreference(PREFERENCE_MUTED)
|
||||
@ -276,6 +276,15 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
this.recipient.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
|
||||
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
||||
|
||||
findPreference(PREFERENCE_TONE).getOnPreferenceChangeListener().onPreferenceChange(findPreference(PREFERENCE_TONE), uri);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeRecipients() {
|
||||
this.recipient = Recipient.from(getActivity(), getArguments().getParcelable(ADDRESS_EXTRA), true);
|
||||
this.recipient.addListener(this);
|
||||
@ -283,7 +292,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
|
||||
private void setSummaries(Recipient recipient) {
|
||||
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
|
||||
AdvancedRingtonePreference ringtonePreference = (AdvancedRingtonePreference) this.findPreference(PREFERENCE_TONE);
|
||||
Preference ringtonePreference = this.findPreference(PREFERENCE_TONE);
|
||||
ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE);
|
||||
ColorPickerPreference colorPreference = (ColorPickerPreference) this.findPreference(PREFERENCE_COLOR);
|
||||
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
|
||||
@ -297,16 +306,13 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
|
||||
if (toneUri == null) {
|
||||
ringtonePreference.setSummary(R.string.preferences__default);
|
||||
ringtonePreference.setCurrentRingtone(Uri.parse(TextSecurePreferences.getNotificationRingtone(getContext())));
|
||||
} else if (toneUri.toString().isEmpty()) {
|
||||
ringtonePreference.setSummary(R.string.preferences__silent);
|
||||
ringtonePreference.setCurrentRingtone(null);
|
||||
} else {
|
||||
Ringtone tone = RingtoneManager.getRingtone(getActivity(), toneUri);
|
||||
|
||||
if (tone != null) {
|
||||
ringtonePreference.setSummary(tone.getTitle(getActivity()));
|
||||
ringtonePreference.setCurrentRingtone(toneUri);
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,6 +374,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
|
||||
if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(value)) {
|
||||
value = null;
|
||||
} else if (value == null) {
|
||||
value = Uri.EMPTY;
|
||||
}
|
||||
|
||||
new AsyncTask<Uri, Void, Void>() {
|
||||
@ -383,6 +391,27 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
}
|
||||
}
|
||||
|
||||
private class RingtoneClickedListener implements Preference.OnPreferenceClickListener {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Uri uri = recipient.getRingtone();
|
||||
|
||||
if (uri == null) uri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
else if (uri.toString().isEmpty()) uri = null;
|
||||
|
||||
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri);
|
||||
|
||||
startActivityForResult(intent, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class VibrateChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
|
@ -9,6 +9,7 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
@ -19,6 +20,9 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public abstract class AbstractNotificationBuilder extends NotificationCompat.Builder {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = AbstractNotificationBuilder.class.getSimpleName();
|
||||
|
||||
protected Context context;
|
||||
protected NotificationPrivacyPreference privacy;
|
||||
|
||||
|
@ -10,8 +10,6 @@ import android.view.View;
|
||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.RingtonePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.RingtonePreferenceDialogFragmentCompat;
|
||||
|
||||
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@ -32,9 +30,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
DialogFragment dialogFragment = null;
|
||||
|
||||
if (preference instanceof RingtonePreference) {
|
||||
dialogFragment = RingtonePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
} else if (preference instanceof ColorPickerPreference) {
|
||||
if (preference instanceof ColorPickerPreference) {
|
||||
dialogFragment = ColorPickerPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomDefaultPreference) {
|
||||
dialogFragment = CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
@ -17,11 +19,13 @@ import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.AdvancedRingtonePreference;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName();
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
@ -44,12 +48,27 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)
|
||||
.setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
|
||||
this.findPreference(TextSecurePreferences.RINGTONE_PREF)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
String current = TextSecurePreferences.getNotificationRingtone(getContext());
|
||||
|
||||
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current == null ? null : Uri.parse(current));
|
||||
|
||||
startActivityForResult(intent, 1);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_COLOR_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.LED_BLINK_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF));
|
||||
initializeRingtoneSummary((AdvancedRingtonePreference) findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,6 +82,16 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__notifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
|
||||
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
||||
|
||||
TextSecurePreferences.setNotificationRingtone(getContext(), uri != null ? uri.toString() : null);
|
||||
initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
}
|
||||
}
|
||||
|
||||
private class RingtoneSummaryListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
@ -72,6 +101,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
preference.setSummary(R.string.preferences__silent);
|
||||
} else {
|
||||
Ringtone tone = RingtoneManager.getRingtone(getActivity(), value);
|
||||
|
||||
if (tone != null) {
|
||||
preference.setSummary(tone.getTitle(getActivity()));
|
||||
}
|
||||
@ -81,7 +111,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeRingtoneSummary(AdvancedRingtonePreference pref) {
|
||||
private void initializeRingtoneSummary(Preference pref) {
|
||||
RingtoneSummaryListener listener = (RingtoneSummaryListener) pref.getOnPreferenceChangeListener();
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
String encodedUri = sharedPreferences.getString(pref.getKey(), null);
|
||||
@ -98,6 +128,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
}
|
||||
|
||||
private class NotificationPrivacyListener extends ListSummaryListener {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object value) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
|
||||
public class AdvancedRingtonePreference extends RingtonePreference {
|
||||
|
||||
private Uri currentRingtone;
|
||||
|
||||
public AdvancedRingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public AdvancedRingtonePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public AdvancedRingtonePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public AdvancedRingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri onRestoreRingtone() {
|
||||
if (currentRingtone == null) return super.onRestoreRingtone();
|
||||
else return currentRingtone;
|
||||
}
|
||||
|
||||
public void setCurrentRingtone(Uri uri) {
|
||||
currentRingtone = uri;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,462 +0,0 @@
|
||||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.v4.content.res.TypedArrayUtils;
|
||||
import android.support.v7.preference.DialogPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* A {@link Preference} that displays a ringtone picker as a dialog.
|
||||
* <p>
|
||||
* This preference will save the picked ringtone's URI as a string into the SharedPreferences. The
|
||||
* saved URI can be fed directly into {@link RingtoneManager#getRingtone(Context, Uri)} to get the
|
||||
* {@link Ringtone} instance that can be played.
|
||||
*
|
||||
* @see RingtoneManager
|
||||
* @see Ringtone
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess,unused")
|
||||
public class RingtonePreference extends DialogPreference {
|
||||
private static final int CUSTOM_RINGTONE_REQUEST_CODE = 0x9000;
|
||||
private static final int WRITE_FILES_PERMISSION_REQUEST_CODE = 0x9001;
|
||||
|
||||
private int ringtoneType;
|
||||
private boolean showDefault;
|
||||
private boolean showSilent;
|
||||
private boolean showAdd;
|
||||
|
||||
private Uri ringtoneUri;
|
||||
|
||||
// private CharSequence summaryHasRingtone;
|
||||
// private CharSequence summary;
|
||||
|
||||
private int miscCustomRingtoneRequestCode = CUSTOM_RINGTONE_REQUEST_CODE;
|
||||
private int miscPermissionRequestCode = WRITE_FILES_PERMISSION_REQUEST_CODE;
|
||||
|
||||
@IntDef({
|
||||
RingtoneManager.TYPE_ALL,
|
||||
RingtoneManager.TYPE_ALARM,
|
||||
RingtoneManager.TYPE_NOTIFICATION,
|
||||
RingtoneManager.TYPE_RINGTONE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
protected @interface RingtoneType {
|
||||
}
|
||||
|
||||
// static {
|
||||
// PreferenceFragmentCompat.addDialogPreference(RingtonePreference.class, RingtonePreferenceDialogFragmentCompat.class);
|
||||
// }
|
||||
|
||||
public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
android.preference.RingtonePreference proxyPreference;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
proxyPreference = new android.preference.RingtonePreference(context, attrs, defStyleAttr, defStyleRes);
|
||||
} else {
|
||||
proxyPreference = new android.preference.RingtonePreference(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
ringtoneType = proxyPreference.getRingtoneType();
|
||||
showDefault = proxyPreference.getShowDefault();
|
||||
showSilent = proxyPreference.getShowSilent();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RingtonePreference, defStyleAttr, 0);
|
||||
showAdd = a.getBoolean(R.styleable.RingtonePreference_showAdd, true);
|
||||
// summaryHasRingtone = a.getText(R.styleable.RingtonePreference_summaryHasRingtone);
|
||||
a.recycle();
|
||||
|
||||
// summary = super.getSummary();
|
||||
}
|
||||
|
||||
public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
public RingtonePreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
|
||||
android.R.attr.dialogPreferenceStyle));
|
||||
}
|
||||
|
||||
public RingtonePreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound type(s) that are shown in the picker.
|
||||
*
|
||||
* @return The sound type(s) that are shown in the picker.
|
||||
* @see #setRingtoneType(int)
|
||||
*/
|
||||
@RingtoneType
|
||||
public int getRingtoneType() {
|
||||
return ringtoneType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sound type(s) that are shown in the picker. See {@link RingtoneManager} for the
|
||||
* possible values.
|
||||
*
|
||||
* @param ringtoneType The sound type(s) that are shown in the picker.
|
||||
*/
|
||||
public void setRingtoneType(@RingtoneType int ringtoneType) {
|
||||
this.ringtoneType = ringtoneType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to a show an item for the default sound/ringtone.
|
||||
*
|
||||
* @return Whether to show an item for the default sound/ringtone.
|
||||
*/
|
||||
public boolean getShowDefault() {
|
||||
return showDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an item for the default sound/ringtone. The default
|
||||
* to use will be deduced from the sound type(s) being shown.
|
||||
*
|
||||
* @param showDefault Whether to show the default or not.
|
||||
*/
|
||||
public void setShowDefault(boolean showDefault) {
|
||||
this.showDefault = showDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to a show an item for 'None'.
|
||||
*
|
||||
* @return Whether to show an item for 'None'.
|
||||
*/
|
||||
public boolean getShowSilent() {
|
||||
return showSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an item for 'None'.
|
||||
*
|
||||
* @param showSilent Whether to show 'None'.
|
||||
*/
|
||||
public void setShowSilent(boolean showSilent) {
|
||||
this.showSilent = showSilent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to a show an item for 'Add new ringtone'.
|
||||
* <p>
|
||||
* Note that this requires {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}. If it's
|
||||
* not supplied in the manifest, the item won't be displayed.
|
||||
*
|
||||
* @return Whether to show an item for 'Add new ringtone'.
|
||||
*/
|
||||
public boolean getShowAdd() {
|
||||
return showAdd;
|
||||
}
|
||||
|
||||
boolean shouldShowAdd() {
|
||||
if (showAdd) {
|
||||
try {
|
||||
PackageInfo pInfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
String[] permissions = pInfo.requestedPermissions;
|
||||
for (String permission : permissions) {
|
||||
if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show an item for 'Add new ringtone'.
|
||||
* <p>
|
||||
* Note that this requires {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}. If it's
|
||||
* not supplied in the manifest, the item won't be displayed.
|
||||
*
|
||||
* @param showAdd Whether to show 'Add new ringtone'.
|
||||
*/
|
||||
public void setShowAdd(boolean showAdd) {
|
||||
this.showAdd = showAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This request code will be used to start the file picker activity that the user can use
|
||||
* to add new ringtones. The new ringtone will be delivered to
|
||||
* {@link RingtonePreferenceDialogFragmentCompat#onActivityResult(int, int, Intent)}.
|
||||
* <p>
|
||||
* The default value equals to {@link #CUSTOM_RINGTONE_REQUEST_CODE}
|
||||
* ({@value #CUSTOM_RINGTONE_REQUEST_CODE}).
|
||||
*/
|
||||
public int getCustomRingtoneRequestCode() {
|
||||
return miscCustomRingtoneRequestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request code that will be used to start the file picker activity that the user can
|
||||
* use to add new ringtones. The new ringtone will be delivered to
|
||||
* {@link RingtonePreferenceDialogFragmentCompat#onActivityResult(int, int, Intent)}.
|
||||
* <p>
|
||||
* The default value equals to {@link #CUSTOM_RINGTONE_REQUEST_CODE}
|
||||
* ({@value #CUSTOM_RINGTONE_REQUEST_CODE}).
|
||||
*
|
||||
* @param customRingtoneRequestCode the request code for the file picker
|
||||
*/
|
||||
public void setCustomRingtoneRequestCode(int customRingtoneRequestCode) {
|
||||
this.miscCustomRingtoneRequestCode = customRingtoneRequestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This request code will be used to ask for user permission to save (write) new ringtone
|
||||
* to one of the public external storage directories (only applies to API 23+). The result will
|
||||
* be delivered to
|
||||
* {@link RingtonePreferenceDialogFragmentCompat#onRequestPermissionsResult(int, String[], int[])}.
|
||||
* <p>
|
||||
* The default value equals to {@link #WRITE_FILES_PERMISSION_REQUEST_CODE}
|
||||
* ({@value #WRITE_FILES_PERMISSION_REQUEST_CODE}).
|
||||
*/
|
||||
public int getPermissionRequestCode() {
|
||||
return miscPermissionRequestCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request code that will be used to ask for user permission to save (write) new
|
||||
* ringtone to one of the public external storage directories (only applies to API 23+). The
|
||||
* result will be delivered to
|
||||
* {@link RingtonePreferenceDialogFragmentCompat#onRequestPermissionsResult(int, String[], int[])}.
|
||||
* <p>
|
||||
* The default value equals to {@link #WRITE_FILES_PERMISSION_REQUEST_CODE}
|
||||
* ({@value #WRITE_FILES_PERMISSION_REQUEST_CODE}).
|
||||
*
|
||||
* @param permissionRequestCode the request code for the file picker
|
||||
*/
|
||||
public void setPermissionRequestCode(int permissionRequestCode) {
|
||||
this.miscPermissionRequestCode = permissionRequestCode;
|
||||
}
|
||||
|
||||
public Uri getRingtone() {
|
||||
return onRestoreRingtone();
|
||||
}
|
||||
|
||||
public void setRingtone(Uri uri) {
|
||||
setInternalRingtone(uri, false);
|
||||
}
|
||||
|
||||
private void setInternalRingtone(Uri uri, boolean force) {
|
||||
Uri oldUri = onRestoreRingtone();
|
||||
|
||||
final boolean changed = (oldUri != null && !oldUri.equals(uri)) || (uri != null && !uri.equals(oldUri));
|
||||
|
||||
if (changed || force) {
|
||||
final boolean wasBlocking = shouldDisableDependents();
|
||||
|
||||
ringtoneUri = uri;
|
||||
onSaveRingtone(uri);
|
||||
|
||||
final boolean isBlocking = shouldDisableDependents();
|
||||
|
||||
notifyChanged();
|
||||
|
||||
if (isBlocking != wasBlocking) {
|
||||
notifyDependencyChange(isBlocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a ringtone is chosen.
|
||||
* <p>
|
||||
* By default, this saves the ringtone URI to the persistent storage as a
|
||||
* string.
|
||||
*
|
||||
* @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
|
||||
*/
|
||||
protected void onSaveRingtone(Uri ringtoneUri) {
|
||||
persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the chooser is about to be shown and the current ringtone
|
||||
* should be marked. Can return null to not mark any ringtone.
|
||||
* <p>
|
||||
* By default, this restores the previous ringtone URI from the persistent
|
||||
* storage.
|
||||
*
|
||||
* @return The ringtone to be marked as the current ringtone.
|
||||
*/
|
||||
protected Uri onRestoreRingtone() {
|
||||
final String uriString = getPersistedString(ringtoneUri == null ? null : ringtoneUri.toString());
|
||||
return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValueObj) {
|
||||
final String defaultValue = (String) defaultValueObj;
|
||||
setInternalRingtone(restoreValue ? onRestoreRingtone() : (!TextUtils.isEmpty(defaultValue) ? Uri.parse(defaultValue) : null), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDisableDependents() {
|
||||
return super.shouldDisableDependents() || onRestoreRingtone() == null;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Returns the summary of this Preference. If no {@code summaryHasRingtone} is set, this will be
|
||||
// * displayed if no ringtone is selected; otherwise the ringtone title will be used.
|
||||
// *
|
||||
// * @return The summary.
|
||||
// */
|
||||
// @Override
|
||||
// public CharSequence getSummary() {
|
||||
// if (ringtoneUri == null) {
|
||||
// return summary;
|
||||
// } else {
|
||||
// String ringtoneTitle = getRingtoneTitle();
|
||||
// if (summaryHasRingtone != null && ringtoneTitle != null) {
|
||||
// return String.format(summaryHasRingtone.toString(), ringtoneTitle);
|
||||
// } else if (ringtoneTitle != null) {
|
||||
// return ringtoneTitle;
|
||||
// } else {
|
||||
// return summary;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Sets the summary for this Preference with a CharSequence. If no {@code summaryHasRingtone} is
|
||||
// * set, this will be displayed if no ringtone is selected; otherwise the ringtone title will be
|
||||
// * used.
|
||||
// *
|
||||
// * @param summary The summary for the preference.
|
||||
// */
|
||||
// @Override
|
||||
// public void setSummary(CharSequence summary) {
|
||||
// super.setSummary(summary);
|
||||
// if (summary == null && this.summary != null) {
|
||||
// this.summary = null;
|
||||
// } else if (summary != null && !summary.equals(this.summary)) {
|
||||
// this.summary = summary.toString();
|
||||
// }
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Returns the picked summary for this Preference. This will be displayed if the preference
|
||||
// * has a persisted value or the default value is set. If the summary
|
||||
// * has a {@linkplain java.lang.String#format String formatting}
|
||||
// * marker in it (i.e. "%s" or "%1$s"), then the current ringtone's title
|
||||
// * will be substituted in its place.
|
||||
// *
|
||||
// * @return The picked summary.
|
||||
// */
|
||||
// @Nullable
|
||||
// public CharSequence getSummaryHasRingtone() {
|
||||
// return summaryHasRingtone;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Sets the picked summary for this Preference with a resource ID. This will be displayed if the
|
||||
// * preference has a persisted value or the default value is set. If the summary
|
||||
// * has a {@linkplain java.lang.String#format String formatting}
|
||||
// * marker in it (i.e. "%s" or "%1$s"), then the current ringtone's title
|
||||
// * will be substituted in its place.
|
||||
// *
|
||||
// * @param resId The summary as a resource.
|
||||
// * @see #setSummaryHasRingtone(CharSequence)
|
||||
// */
|
||||
// public void setSummaryHasRingtone(@StringRes int resId) {
|
||||
// setSummaryHasRingtone(getContext().getString(resId));
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Sets the picked summary for this Preference with a CharSequence. This will be displayed if
|
||||
// * the preference has a persisted value or the default value is set. If the summary
|
||||
// * has a {@linkplain java.lang.String#format String formatting}
|
||||
// * marker in it (i.e. "%s" or "%1$s"), then the current ringtone's title
|
||||
// * will be substituted in its place.
|
||||
// *
|
||||
// * @param summaryHasRingtone The summary for the preference.
|
||||
// */
|
||||
// public void setSummaryHasRingtone(@Nullable CharSequence summaryHasRingtone) {
|
||||
// if (summaryHasRingtone == null && this.summaryHasRingtone != null) {
|
||||
// this.summaryHasRingtone = null;
|
||||
// } else if (summaryHasRingtone != null && !summaryHasRingtone.equals(this.summaryHasRingtone)) {
|
||||
// this.summaryHasRingtone = summaryHasRingtone.toString();
|
||||
// }
|
||||
//
|
||||
// notifyChanged();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns the selected ringtone's title, or {@code null} if no ringtone is picked.
|
||||
*
|
||||
* @return The selected ringtone's title, or {@code null} if no ringtone is picked.
|
||||
*/
|
||||
public String getRingtoneTitle() {
|
||||
Context context = getContext();
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
String[] projection = {MediaStore.MediaColumns.TITLE};
|
||||
|
||||
String ringtoneTitle = null;
|
||||
|
||||
if (ringtoneUri != null) {
|
||||
int type = RingtoneManager.getDefaultType(ringtoneUri);
|
||||
|
||||
switch (type) {
|
||||
case RingtoneManager.TYPE_ALL:
|
||||
case RingtoneManager.TYPE_RINGTONE:
|
||||
ringtoneTitle = context.getString(R.string.RingtonePreference_ringtone_default);
|
||||
break;
|
||||
case RingtoneManager.TYPE_ALARM:
|
||||
ringtoneTitle = context.getString(R.string.RingtonePreference_alarm_sound_default);
|
||||
break;
|
||||
case RingtoneManager.TYPE_NOTIFICATION:
|
||||
ringtoneTitle = context.getString(R.string.RingtonePreference_notification_sound_default);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
Cursor cursor = cr.query(ringtoneUri, projection, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
ringtoneTitle = cursor.getString(0);
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ringtoneTitle;
|
||||
}
|
||||
}
|
@ -1,593 +0,0 @@
|
||||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.HeaderViewListAdapter;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class RingtonePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
|
||||
private static final String TAG = "RingtonePrefDialog";
|
||||
|
||||
private static final String CURSOR_DEFAULT_ID = "-2";
|
||||
private static final String CURSOR_NONE_ID = "-1";
|
||||
|
||||
private int selectedIndex = -1;
|
||||
private String[] data;
|
||||
|
||||
private RingtoneManager ringtoneManager;
|
||||
private Ringtone defaultRingtone;
|
||||
|
||||
public static RingtonePreferenceDialogFragmentCompat newInstance(String key) {
|
||||
RingtonePreferenceDialogFragmentCompat fragment = new RingtonePreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private RingtonePreference getRingtonePreference() {
|
||||
return (RingtonePreference) getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
stopPlaying();
|
||||
}
|
||||
|
||||
private void stopPlaying() {
|
||||
if (defaultRingtone != null && defaultRingtone.isPlaying()) {
|
||||
defaultRingtone.stop();
|
||||
}
|
||||
|
||||
if (ringtoneManager != null) {
|
||||
ringtoneManager.stopPreviousRingtone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
RingtonePreference ringtonePreference = getRingtonePreference();
|
||||
|
||||
createRingtoneList(ringtonePreference.getRingtone());
|
||||
|
||||
final Context context = getContext();
|
||||
|
||||
final int ringtoneType = ringtonePreference.getRingtoneType();
|
||||
final boolean showDefault = ringtonePreference.getShowDefault();
|
||||
final boolean showSilent = ringtonePreference.getShowSilent();
|
||||
final Uri defaultUri;
|
||||
|
||||
if (showDefault) {
|
||||
defaultUri = RingtoneManager.getDefaultUri(ringtoneType);
|
||||
} else {
|
||||
defaultUri = null;
|
||||
}
|
||||
|
||||
builder
|
||||
.setSingleChoiceItems(data, selectedIndex, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
if (i < data.length) {
|
||||
selectedIndex = i;
|
||||
|
||||
int realIdx = i - (showDefault ? 1 : 0) - (showSilent ? 1 : 0);
|
||||
|
||||
stopPlaying();
|
||||
|
||||
if (showDefault && i == 0) {
|
||||
if (defaultRingtone != null) {
|
||||
defaultRingtone.play();
|
||||
} else {
|
||||
defaultRingtone = RingtoneManager.getRingtone(context, defaultUri);
|
||||
if (defaultRingtone != null) {
|
||||
defaultRingtone.play();
|
||||
}
|
||||
}
|
||||
} else if (((showDefault && i == 1) || (!showDefault && i == 0)) && showSilent) {
|
||||
ringtoneManager.stopPreviousRingtone(); // "playing" silence
|
||||
} else {
|
||||
Ringtone ringtone = ringtoneManager.getRingtone(realIdx);
|
||||
ringtone.play();
|
||||
}
|
||||
} else {
|
||||
newRingtone();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
if (defaultRingtone != null) {
|
||||
defaultRingtone.stop();
|
||||
}
|
||||
|
||||
RingtonePreferenceDialogFragmentCompat.this.onDismiss(dialogInterface);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, this)
|
||||
.setPositiveButton(android.R.string.ok, this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog dialog = (AlertDialog) super.onCreateDialog(savedInstanceState);
|
||||
|
||||
if (getRingtonePreference().shouldShowAdd()) {
|
||||
ListView listView = dialog.getListView();
|
||||
View addRingtoneView = LayoutInflater.from(listView.getContext()).inflate(R.layout.add_ringtone_item, listView, false);
|
||||
listView.addFooterView(addRingtoneView);
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
stopPlaying();
|
||||
|
||||
defaultRingtone = null;
|
||||
|
||||
final RingtonePreference preference = getRingtonePreference();
|
||||
final boolean showDefault = preference.getShowDefault();
|
||||
final boolean showSilent = preference.getShowSilent();
|
||||
|
||||
if (positiveResult && selectedIndex >= 0) {
|
||||
final Uri uri;
|
||||
|
||||
if (showDefault && selectedIndex == 0) {
|
||||
uri = RingtoneManager.getDefaultUri(preference.getRingtoneType());
|
||||
} else if (((showDefault && selectedIndex == 1) || (!showDefault && selectedIndex == 0)) && showSilent) {
|
||||
uri = null;
|
||||
} else {
|
||||
uri = ringtoneManager.getRingtoneUri(selectedIndex - (showDefault ? 1 : 0) - (showSilent ? 1 : 0));
|
||||
}
|
||||
|
||||
if (preference.callChangeListener(uri)) {
|
||||
preference.setRingtone(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String[] createRingtoneList(Uri ringtoneUri) {
|
||||
RingtonePreference ringtonePreference = getRingtonePreference();
|
||||
List<String> results = new LinkedList<>();
|
||||
|
||||
ringtoneManager = new RingtoneManager(getContext());
|
||||
ringtoneManager.setType(ringtonePreference.getRingtoneType());
|
||||
ringtoneManager.setStopPreviousRingtone(true);
|
||||
|
||||
Cursor ringtoneCursor = ringtoneManager.getCursor();
|
||||
|
||||
String colId = ringtoneCursor.getColumnName(RingtoneManager.ID_COLUMN_INDEX);
|
||||
String colTitle = ringtoneCursor.getColumnName(RingtoneManager.TITLE_COLUMN_INDEX);
|
||||
|
||||
MatrixCursor extras = new MatrixCursor(new String[]{colId, colTitle});
|
||||
|
||||
final int ringtoneType = ringtonePreference.getRingtoneType();
|
||||
final boolean showDefault = ringtonePreference.getShowDefault();
|
||||
final boolean showSilent = ringtonePreference.getShowSilent();
|
||||
|
||||
if (showDefault) {
|
||||
switch (ringtoneType) {
|
||||
case RingtoneManager.TYPE_ALARM:
|
||||
extras.addRow(new String[]{CURSOR_DEFAULT_ID, getString(R.string.RingtonePreference_alarm_sound_default)});
|
||||
break;
|
||||
case RingtoneManager.TYPE_NOTIFICATION:
|
||||
extras.addRow(new String[]{CURSOR_DEFAULT_ID, getString(R.string.RingtonePreference_notification_sound_default)});
|
||||
break;
|
||||
case RingtoneManager.TYPE_RINGTONE:
|
||||
case RingtoneManager.TYPE_ALL:
|
||||
default:
|
||||
extras.addRow(new String[]{CURSOR_DEFAULT_ID, getString(R.string.RingtonePreference_ringtone_default)});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (showSilent) {
|
||||
extras.addRow(new String[]{CURSOR_NONE_ID, getString(R.string.RingtonePreference_ringtone_silent)});
|
||||
}
|
||||
|
||||
selectedIndex = ringtoneManager.getRingtonePosition(ringtoneUri);
|
||||
if (selectedIndex >= 0) {
|
||||
selectedIndex += (showDefault ? 1 : 0) + (showSilent ? 1 : 0);
|
||||
}
|
||||
|
||||
if (selectedIndex < 0 && showDefault) {
|
||||
if (RingtoneManager.getDefaultType(ringtoneUri) != -1) {
|
||||
selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedIndex < 0 && showSilent) {
|
||||
selectedIndex = showDefault ? 1 : 0;
|
||||
}
|
||||
|
||||
Cursor[] cursors = {extras, ringtoneCursor};
|
||||
Cursor cursor = new MergeCursor(cursors);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(cursor.getString(1));
|
||||
}
|
||||
|
||||
return data = results.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == getRingtonePreference().getCustomRingtoneRequestCode()) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
final Uri fileUri = data.getData();
|
||||
final Context context = getContext();
|
||||
|
||||
final RingtonePreference ringtonePreference = getRingtonePreference();
|
||||
final int ringtoneType = ringtonePreference.getRingtoneType();
|
||||
|
||||
// FIXME static field leak
|
||||
@SuppressLint("StaticFieldLeak") final AsyncTask<Uri, Void, String[]> installTask = new AsyncTask<Uri, Void, String[]>() {
|
||||
@Override
|
||||
protected String[] doInBackground(Uri... params) {
|
||||
try {
|
||||
Uri newUri = addCustomExternalRingtone(context, params[0], ringtoneType);
|
||||
|
||||
return createRingtoneList(newUri);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to add new ringtone: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final String[] newData) {
|
||||
if (newData != null) {
|
||||
final ListView listView = ((AlertDialog) getDialog()).getListView();
|
||||
ArrayAdapter<String> adapter = (ArrayAdapter<String>)((HeaderViewListAdapter)listView.getAdapter()).getWrappedAdapter();
|
||||
|
||||
adapter.clear();
|
||||
adapter.addAll(newData);
|
||||
|
||||
listView.setItemChecked(selectedIndex, true);
|
||||
listView.setSelection(selectedIndex);
|
||||
listView.clearFocus();
|
||||
} else {
|
||||
Toast.makeText(context, getString(R.string.RingtonePreference_unable_to_add_ringtone), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
installTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, fileUri);
|
||||
} else {
|
||||
ListView listView = ((AlertDialog) getDialog()).getListView();
|
||||
listView.setItemChecked(selectedIndex, true);
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == getRingtonePreference().getPermissionRequestCode() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
newRingtone();
|
||||
} else {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
private void newRingtone() {
|
||||
boolean hasPerm = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
if (hasPerm) {
|
||||
final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
chooseFile.setType("audio/*");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"audio/*", "application/ogg"});
|
||||
}
|
||||
startActivityForResult(chooseFile, getRingtonePreference().getCustomRingtoneRequestCode());
|
||||
} else {
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, getRingtonePreference().getPermissionRequestCode());
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Uri addCustomExternalRingtone(Context context, @NonNull Uri fileUri, final int type)
|
||||
throws IOException {
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
throw new IOException("External storage is not mounted. Unable to install ringtones.");
|
||||
}
|
||||
|
||||
if (ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
|
||||
fileUri = Uri.fromFile(new File(fileUri.getPath()));
|
||||
}
|
||||
|
||||
String mimeType = context.getContentResolver().getType(fileUri);
|
||||
|
||||
if (mimeType == null) {
|
||||
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(fileUri
|
||||
.toString());
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
|
||||
fileExtension.toLowerCase());
|
||||
}
|
||||
|
||||
if (mimeType == null || !(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
|
||||
throw new IllegalArgumentException("Ringtone file must have MIME type \"audio/*\"."
|
||||
+ " Given file has MIME type \"" + mimeType + "\"");
|
||||
}
|
||||
|
||||
final String subdirectory = getDirForType(type);
|
||||
|
||||
final File outFile = getUniqueExternalFile(context, subdirectory, getFileDisplayNameFromUri(context, fileUri), mimeType);
|
||||
|
||||
if (outFile != null) {
|
||||
final InputStream input = context.getContentResolver().openInputStream(fileUri);
|
||||
final OutputStream output = new FileOutputStream(outFile);
|
||||
|
||||
if (input != null) {
|
||||
byte[] buffer = new byte[10240];
|
||||
|
||||
for (int len; (len = input.read(buffer)) != -1; ) {
|
||||
output.write(buffer, 0, len);
|
||||
}
|
||||
|
||||
input.close();
|
||||
}
|
||||
|
||||
output.close();
|
||||
|
||||
NewRingtoneScanner scanner = null;
|
||||
try {
|
||||
scanner = new NewRingtoneScanner(context, outFile);
|
||||
return scanner.take();
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Audio file failed to scan as a ringtone", e);
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDirForType(int type) {
|
||||
switch (type) {
|
||||
case RingtoneManager.TYPE_ALL:
|
||||
case RingtoneManager.TYPE_RINGTONE:
|
||||
return Environment.DIRECTORY_RINGTONES;
|
||||
case RingtoneManager.TYPE_NOTIFICATION:
|
||||
return Environment.DIRECTORY_NOTIFICATIONS;
|
||||
case RingtoneManager.TYPE_ALARM:
|
||||
return Environment.DIRECTORY_ALARMS;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported ringtone type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFileDisplayNameFromUri(Context context, Uri uri) {
|
||||
String scheme = uri.getScheme();
|
||||
|
||||
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||||
return uri.getLastPathSegment();
|
||||
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
|
||||
|
||||
String[] projection = {OpenableColumns.DISPLAY_NAME};
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will only happen if the Uri isn't either SCHEME_CONTENT or SCHEME_FILE, so we assume
|
||||
// it already represents the file's name.
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file in the specified external storage with the desired name. If the name is
|
||||
* taken, the new file's name will have '(%d)' to avoid overwriting files.
|
||||
*
|
||||
* @param context {@link Context} to query the file name from.
|
||||
* @param subdirectory One of the directories specified in {@link android.os.Environment}
|
||||
* @param fileName desired name for the file.
|
||||
* @param mimeType MIME type of the file to create.
|
||||
* @return the File object in the storage, or null if an error occurs.
|
||||
*/
|
||||
@Nullable
|
||||
private static File getUniqueExternalFile(Context context, String subdirectory, String fileName,
|
||||
String mimeType) {
|
||||
File externalStorage = Environment.getExternalStoragePublicDirectory(subdirectory);
|
||||
// Make sure the storage subdirectory exists
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
externalStorage.mkdirs();
|
||||
|
||||
File outFile;
|
||||
try {
|
||||
// Ensure the file has a unique name, as to not override any existing file
|
||||
outFile = buildUniqueFile(externalStorage, mimeType, fileName);
|
||||
} catch (FileNotFoundException e) {
|
||||
// This might also be reached if the number of repeated files gets too high
|
||||
Log.e(TAG, "Unable to get a unique file name: " + e);
|
||||
return null;
|
||||
}
|
||||
return outFile;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static File buildUniqueFile(File externalStorage, String mimeType, String fileName) throws FileNotFoundException {
|
||||
final String[] parts = splitFileName(mimeType, fileName);
|
||||
|
||||
String name = parts[0];
|
||||
String ext = (parts[1] != null) ? "." + parts[1] : "";
|
||||
|
||||
File file = new File(externalStorage, name + ext);
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
int n = 0;
|
||||
while (file.exists()) {
|
||||
if (n++ >= 32) {
|
||||
n = random.nextInt();
|
||||
}
|
||||
file = new File(externalStorage, name + " (" + n + ")" + ext);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String[] splitFileName(String mimeType, String displayName) {
|
||||
String name;
|
||||
String ext;
|
||||
|
||||
String mimeTypeFromExt;
|
||||
|
||||
// Extract requested extension from display name
|
||||
final int lastDot = displayName.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
name = displayName.substring(0, lastDot);
|
||||
ext = displayName.substring(lastDot + 1);
|
||||
mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
|
||||
ext.toLowerCase());
|
||||
} else {
|
||||
name = displayName;
|
||||
ext = null;
|
||||
mimeTypeFromExt = null;
|
||||
}
|
||||
|
||||
if (mimeTypeFromExt == null) {
|
||||
mimeTypeFromExt = "application/octet-stream";
|
||||
}
|
||||
|
||||
final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
|
||||
mimeType);
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (TextUtils.equals(mimeType, mimeTypeFromExt) || TextUtils.equals(ext, extFromMimeType)) {
|
||||
// Extension maps back to requested MIME type; allow it
|
||||
} else {
|
||||
// No match; insist that create file matches requested MIME
|
||||
name = displayName;
|
||||
ext = extFromMimeType;
|
||||
}
|
||||
|
||||
|
||||
if (ext == null) {
|
||||
ext = "";
|
||||
}
|
||||
|
||||
return new String[]{name, ext};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link android.media.MediaScannerConnection} to scan a ringtone file and add its
|
||||
* information to the internal database.
|
||||
* <p>
|
||||
* It uses a {@link java.util.concurrent.LinkedBlockingQueue} so that the caller can block until
|
||||
* the scan is completed.
|
||||
*/
|
||||
private static class NewRingtoneScanner implements Closeable, MediaScannerConnection.MediaScannerConnectionClient {
|
||||
private MediaScannerConnection mMediaScannerConnection;
|
||||
private File mFile;
|
||||
private LinkedBlockingQueue<Uri> mQueue = new LinkedBlockingQueue<>(1);
|
||||
|
||||
private NewRingtoneScanner(Context context, File file) {
|
||||
mFile = file;
|
||||
mMediaScannerConnection = new MediaScannerConnection(context, this);
|
||||
mMediaScannerConnection.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mMediaScannerConnection.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaScannerConnected() {
|
||||
mMediaScannerConnection.scanFile(mFile.getAbsolutePath(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
if (uri == null) {
|
||||
// There was some issue with scanning. Delete the copied file so it is not oprhaned.
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
mFile.delete();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mQueue.put(uri);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Unable to put new ringtone Uri in queue", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Uri take() throws InterruptedException {
|
||||
return mQueue.take();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,36 +2,34 @@ package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class SignalRingtonePreference extends AdvancedRingtonePreference {
|
||||
public class SignalPreference extends Preference {
|
||||
|
||||
private TextView rightSummary;
|
||||
private CharSequence summary;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public SignalRingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public SignalPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public SignalRingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public SignalPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public SignalRingtonePreference(Context context, AttributeSet attrs) {
|
||||
public SignalPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public SignalRingtonePreference(Context context) {
|
||||
public SignalPreference(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
@ -45,17 +43,17 @@ public class SignalRingtonePreference extends AdvancedRingtonePreference {
|
||||
super.onBindViewHolder(view);
|
||||
|
||||
this.rightSummary = (TextView)view.findViewById(R.id.right_summary);
|
||||
setSummary(summary);
|
||||
setSummary(this.summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
this.summary = summary;
|
||||
|
||||
super.setSummary(null);
|
||||
|
||||
if (rightSummary != null) {
|
||||
rightSummary.setText(summary);
|
||||
this.summary = summary;
|
||||
|
||||
if (this.rightSummary != null) {
|
||||
this.rightSummary.setText(summary);
|
||||
}
|
||||
}
|
||||
|
@ -635,7 +635,17 @@ public class TextSecurePreferences {
|
||||
}
|
||||
|
||||
public static String getNotificationRingtone(Context context) {
|
||||
return getStringPreference(context, RINGTONE_PREF, Settings.System.DEFAULT_NOTIFICATION_URI.toString());
|
||||
String result = getStringPreference(context, RINGTONE_PREF, Settings.System.DEFAULT_NOTIFICATION_URI.toString());
|
||||
|
||||
if (result != null && result.startsWith("file:")) {
|
||||
result = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void setNotificationRingtone(Context context, String ringtone) {
|
||||
setStringPreference(context, RINGTONE_PREF, ringtone);
|
||||
}
|
||||
|
||||
public static boolean isNotificationVibrateEnabled(Context context) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user