mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
Update to V7 PreferencesCompat library
// FREEBIE
This commit is contained in:
parent
cb9bc9659b
commit
a1c276f70b
@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.thoughtcrime.securesms">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message"/>
|
||||
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference"/>
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
||||
android:label="Access to TextSecure Secrets"
|
||||
|
71
build.gradle
71
build.gradle
@ -34,6 +34,9 @@ repositories {
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
maven { // textdrawable
|
||||
url 'https://dl.bintray.com/amulyakhare/maven'
|
||||
}
|
||||
@ -42,18 +45,15 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:appcompat-v7:25.1.0'
|
||||
compile 'com.android.support:recyclerview-v7:25.1.0'
|
||||
compile 'com.android.support:design:25.1.0'
|
||||
compile 'com.android.support:support-v13:25.1.0'
|
||||
compile 'com.android.support:cardview-v7:25.1.0'
|
||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
compile ('com.android.support:gridlayout-v7:25.1.0') {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
compile 'com.android.support:multidex:1.0.1'
|
||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
||||
compile 'com.android.support:recyclerview-v7:25.4.0'
|
||||
compile 'com.android.support:design:25.4.0'
|
||||
compile 'com.android.support:support-v13:25.4.0'
|
||||
compile 'com.android.support:cardview-v7:25.4.0'
|
||||
compile 'com.android.support:preference-v7:25.4.0'
|
||||
compile 'com.android.support:preference-v14:25.4.0'
|
||||
compile 'com.android.support:gridlayout-v7:25.4.0'
|
||||
compile 'com.android.support:multidex:1.0.2'
|
||||
|
||||
compile 'com.google.android.gms:play-services-gcm:9.6.1'
|
||||
compile 'com.google.android.gms:play-services-maps:9.6.1'
|
||||
@ -101,6 +101,10 @@ dependencies {
|
||||
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
||||
}
|
||||
compile 'com.annimon:stream:1.1.8'
|
||||
compile ('com.takisoft.fix:colorpicker:0.9.1') {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||
}
|
||||
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
@ -111,6 +115,8 @@ dependencies {
|
||||
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
||||
testCompile 'org.powermock:powermock-classloading-xstream:1.6.1'
|
||||
|
||||
androidTestCompile 'com.android.support:multidex:1.0.2'
|
||||
androidTestCompile 'com.android.support:multidex-instrumentation:1.0.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
androidTestCompile ('org.assertj:assertj-core:1.7.1') {
|
||||
@ -124,14 +130,15 @@ dependencies {
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:appcompat-v7:b48bfd5efc14da938ba0647f9894aa7d3d90f0b618167652a318f6f336ef303e',
|
||||
'com.android.support:recyclerview-v7:45beed1778f785c75540b68aa7735b8973a518ac21e8d763188dbbdae6c5b65d',
|
||||
'com.android.support:design:92466557dc6a222bbff361801b26979573cb7086119331e78c74a2df34d5e11e',
|
||||
'com.android.support:support-v13:deeb43c2878025f2a0485791f66b5f59a1c6a4c6671c6ad7bb20abfeffaa313f',
|
||||
'com.android.support:cardview-v7:cd6f472f130a75f029cd1b7c56f72174023d56a2eee2b97577837fe39169d5df',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.android.support:gridlayout-v7:c24f2aa68089fd31b39811bc502e24637a6a8cc29bbf709cda4f4605047c9e11',
|
||||
'com.android.support:multidex:60df8e7153fabdcf3a6a48a2ce5980b599cf25cbeb3b689b7b615975ca731840',
|
||||
'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677',
|
||||
'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d',
|
||||
'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966',
|
||||
'com.android.support:support-v13:f2dcf3eb3fe0271038dc78f6cff9cc3256a591f5c5198277b3887480e5b95e79',
|
||||
'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70',
|
||||
'com.android.support:preference-v7:69bfa8e5527585dc51c02393b882c6b7e1e682995d5fbe45f57b7cb4e6970a7b',
|
||||
'com.android.support:preference-v14:aac6e6cb89b70e27859d88cf75410a5feab1c7a9364d420de42ddb7c154a866b',
|
||||
'com.android.support:gridlayout-v7:4c805b95e5b0a39c7244a0d1a14449cc54a9ab242b806f7379b38846f0539ce9',
|
||||
'com.android.support:multidex:7cd48755c7cfdb6dd2d21cbb02236ec390f6ac91cde87eb62f475b259ab5301d',
|
||||
'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
|
||||
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
|
||||
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
|
||||
@ -163,12 +170,14 @@ dependencyVerification {
|
||||
'com.tomergoldst.android:tooltips:4c56697dd1ad64b8066535c61f961a6d901e7ae5d97ae27084ba40ad620349b6',
|
||||
'com.klinkerapps:android-smsmms:e7c3328a0f3a8dd44daa8129de4e99996f3057a4546e47891b036b81e0ebf1d1',
|
||||
'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9',
|
||||
'com.android.support:support-v4:ed4cda7c752f51d33f9bbdfff3422b425b323d356cd1bdc9786aa413c912e594',
|
||||
'com.android.support:support-vector-drawable:2697503d3e8e709023ae176ba5db7f98ca0aa0b4e6290aedcb3c371904806bf7',
|
||||
'com.android.support:animated-vector-drawable:6d05cb63d1f68900220f85c56dfe1066a9bb19cb0ec1247cc68fc2ba32f6b4a7',
|
||||
'com.android.support:support-compat:e880fb1209c33fcb43e2b25716808e1a6e0b4d3170d5a8dc7704e15084428f88',
|
||||
'com.android.support:support-core-ui:0149b54fd3bc9f4b3b2d321ff53c11821b31a2eca1e664d0cee224e8f53073d6',
|
||||
'com.android.support:transition:cf53f778352fe0b74ff14d838bef9fe79264f3fd43eac499b6e0d1664dbd8997',
|
||||
'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1',
|
||||
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
|
||||
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
|
||||
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
|
||||
'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835',
|
||||
'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca',
|
||||
'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3',
|
||||
'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3',
|
||||
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
|
||||
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
|
||||
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
|
||||
@ -177,6 +186,7 @@ dependencyVerification {
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541',
|
||||
'com.google.android:flexbox:a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935',
|
||||
'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086',
|
||||
'org.whispersystems:curve25519-android:82595394422b957d4a5b5f1b27b75ba25cf6dc4db4d312418ca38cd6fff279ca',
|
||||
'org.whispersystems:signal-protocol-java:5152c2b01a25147967d6bf82e540f947901bdfa79260be3eb3e96b03f787d6b5',
|
||||
@ -190,16 +200,15 @@ dependencyVerification {
|
||||
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
||||
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'com.android.support:support-annotations:47a2a30eab487a490a8a8f16678007c3d2b6dcae1e09b0485a12bbf921200ec3',
|
||||
'com.android.support:support-media-compat:8d6a1a5ba3d9eb1a25cb8f21bb312ac6280202e3d2900cb0b447d065d0d8a125',
|
||||
'com.android.support:support-core-utils:a7649e18c04143dde40c218c5ce9a030e7ae674089cd7b18c6cf8ed2a22cf01a',
|
||||
'com.android.support:support-fragment:1294500b357f52cf3779e2521c79f54ae7844f3b9a5f6727495dbbda7f231377',
|
||||
'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8',
|
||||
'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748',
|
||||
'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945',
|
||||
]
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '25.0.0'
|
||||
buildToolsVersion '25.0.2'
|
||||
useLibrary 'org.apache.http.legacy'
|
||||
|
||||
dexOptions {
|
||||
|
13
res/drawable/colorpickerpreference_pref_swatch.xml
Normal file
13
res/drawable/colorpickerpreference_pref_swatch.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" />
|
10
res/drawable/ic_add.xml
Normal file
10
res/drawable/ic_add.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorAccent"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
45
res/layout/add_ringtone_item.xml
Normal file
45
res/layout/add_ringtone_item.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@drawable/ic_add" />
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/add_ringtone_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="20dp"
|
||||
android:ellipsize="marquee"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="3"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingEnd="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding"
|
||||
android:text="@string/RingtonePreference_add_ringtone_text"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?colorAccent" />
|
||||
</LinearLayout>
|
7
res/layout/preference_widget_color_swatch.xml
Normal file
7
res/layout/preference_widget_color_swatch.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/color_picker_widget"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false" />
|
@ -133,14 +133,12 @@
|
||||
<item>@string/preferences__fast</item>
|
||||
<item>@string/preferences__normal</item>
|
||||
<item>@string/preferences__slow</item>
|
||||
<item>@string/preferences__custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_led_blink_pattern_values" translatable="false">
|
||||
<item>300,300</item>
|
||||
<item>500,2000</item>
|
||||
<item>3000,3000</item>
|
||||
<item>custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_repeat_alerts_entries">
|
||||
|
@ -199,4 +199,26 @@
|
||||
<attr name="scaleEmojis" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="RingtonePreference">
|
||||
<attr name="showAdd" format="boolean" />
|
||||
<attr name="summaryHasRingtone" format="string|reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ColorPickerPreference">
|
||||
<attr name="currentColor" format="reference" />
|
||||
|
||||
<attr name="colors" format="reference" />
|
||||
|
||||
<attr name="sortColors" format="boolean|reference" />
|
||||
|
||||
<attr name="colorDescriptions" format="reference" />
|
||||
|
||||
<attr name="columns" format="integer|reference" />
|
||||
|
||||
<attr name="colorSize" format="enum|reference">
|
||||
<enum name="large" value="1" />
|
||||
<enum name="small" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
@ -1460,6 +1460,14 @@
|
||||
<string name="preferences__read_receipts">Read receipts</string>
|
||||
<string name="preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts">If you read receipts are disabled, you won\'t be able to see read receipts from others.</string>
|
||||
|
||||
<string name="RingtonePreference_ringtone_default">Default ringtone</string>
|
||||
<string name="RingtonePreference_ringtone_silent">None</string>
|
||||
<string name="RingtonePreference_ringtone_picker_title">Ringtones</string>
|
||||
<string name="RingtonePreference_notification_sound_default">Default notification sound</string>
|
||||
<string name="RingtonePreference_alarm_sound_default">Default alarm sound</string>
|
||||
<string name="RingtonePreference_add_ringtone_text">Add ringtone</string>
|
||||
<string name="RingtonePreference_unable_to_add_ringtone">Unable to add custom ringtone</string>
|
||||
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
|
@ -256,4 +256,7 @@
|
||||
<item name="android:focusable">false</item>
|
||||
</style>
|
||||
|
||||
<style name="PreferenceThemeOverlay.Fix" parent="PreferenceThemeOverlay.v14.Material">
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -18,6 +18,8 @@
|
||||
<item name="contact_selection_lay_user">#a0000000</item>
|
||||
<item name="contact_selection_header_text">@color/textsecure_primary_dark</item>
|
||||
<item name="dialog_background_color">@color/background_material_light</item>
|
||||
<item name="pref_divider">@drawable/preference_divider_light</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkNoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
|
||||
@ -34,6 +36,8 @@
|
||||
<item name="contact_selection_lay_user">#afeeeeee</item>
|
||||
<item name="contact_selection_header_text">#66eeeeee</item>
|
||||
<item name="dialog_background_color">@color/background_material_dark</item>
|
||||
<item name="pref_divider">@drawable/preference_divider_dark</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.HighlightTheme" parent="@style/TextSecure.LightTheme">
|
||||
@ -212,6 +216,7 @@
|
||||
<item name="app_protect_timeout_picker_color">@style/BetterPickersDialogFragment.Light</item>
|
||||
|
||||
<item name="group_members_dialog_icon">@drawable/ic_group_grey600_24dp</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkTheme" parent="@style/Theme.AppCompat">
|
||||
@ -335,5 +340,6 @@
|
||||
<item name="app_protect_timeout_picker_color">@style/BetterPickersDialogFragment</item>
|
||||
|
||||
<item name="group_members_dialog_icon">@drawable/ic_group_white_24dp</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
@ -30,7 +30,7 @@
|
||||
android:entries="@array/pref_led_color_entries"
|
||||
android:entryValues="@array/pref_led_color_values" />
|
||||
|
||||
<org.thoughtcrime.securesms.preferences.LedBlinkPatternListPreference
|
||||
<org.thoughtcrime.securesms.preferences.SignalListPreference
|
||||
android:key="pref_led_blink"
|
||||
android:defaultValue="500,2000"
|
||||
android:title="@string/preferences__pref_led_blink_title"
|
||||
|
@ -25,16 +25,17 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
||||
@ -138,7 +139,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
MasterSecret masterSecret = getArguments().getParcelable("master_secret");
|
||||
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
||||
@ -163,6 +163,11 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -1,11 +1,13 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.preference.DialogPreference;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.DialogPreference;
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@ -19,6 +21,7 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.CustomPreferenceValidator;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.net.URI;
|
||||
@ -36,11 +39,6 @@ public class CustomDefaultPreference extends DialogPreference {
|
||||
private CustomPreferenceValidator validator;
|
||||
private String defaultValue;
|
||||
|
||||
private Spinner spinner;
|
||||
private EditText customText;
|
||||
private TextView defaultLabel;
|
||||
private Button positiveButton;
|
||||
|
||||
public CustomDefaultPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
@ -50,7 +48,7 @@ public class CustomDefaultPreference extends DialogPreference {
|
||||
this.inputType = attributes.getInt(0, 0);
|
||||
this.customPreference = getKey();
|
||||
this.customToggle = attributes.getString(1);
|
||||
this.validator = new NullValidator();
|
||||
this.validator = new CustomDefaultPreferenceDialogFragmentCompat.NullValidator();
|
||||
|
||||
attributes.recycle();
|
||||
|
||||
@ -80,40 +78,6 @@ public class CustomDefaultPreference extends DialogPreference {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(@NonNull View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
this.spinner = (Spinner) view.findViewById(R.id.default_or_custom);
|
||||
this.defaultLabel = (TextView) view.findViewById(R.id.default_label);
|
||||
this.customText = (EditText) view.findViewById(R.id.custom_edit);
|
||||
|
||||
this.customText.setInputType(inputType);
|
||||
this.customText.addTextChangedListener(new TextValidator());
|
||||
this.customText.setText(getCustomValue());
|
||||
this.spinner.setOnItemSelectedListener(new SelectionLister());
|
||||
this.defaultLabel.setText(getPrettyPrintValue(defaultValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDialog(Bundle instanceState) {
|
||||
super.showDialog(instanceState);
|
||||
positiveButton = ((AlertDialog)getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
if (isCustom()) spinner.setSelection(1, true);
|
||||
else spinner.setSelection(0, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult) {
|
||||
if (spinner != null) setCustom(spinner.getSelectedItemPosition() == 1);
|
||||
if (customText != null) setCustomValue(customText.getText().toString());
|
||||
|
||||
setSummary(getSummary());
|
||||
}
|
||||
}
|
||||
|
||||
private String getPrettyPrintValue(String value) {
|
||||
if (TextUtils.isEmpty(value)) return getContext().getString(R.string.CustomDefaultPreference_none);
|
||||
else return value;
|
||||
@ -139,87 +103,155 @@ public class CustomDefaultPreference extends DialogPreference {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private class SelectionLister implements AdapterView.OnItemSelectedListener {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
defaultLabel.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
|
||||
customText.setVisibility(position == 0 ? View.GONE : View.VISIBLE);
|
||||
positiveButton.setEnabled(position == 0 || validator.isValid(customText.getText().toString()));
|
||||
public static class CustomDefaultPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
|
||||
|
||||
private static final String INPUT_TYPE = "input_type";
|
||||
|
||||
private Spinner spinner;
|
||||
private EditText customText;
|
||||
private TextView defaultLabel;
|
||||
|
||||
public static CustomDefaultPreferenceDialogFragmentCompat newInstance(String key) {
|
||||
CustomDefaultPreferenceDialogFragmentCompat fragment = new CustomDefaultPreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
defaultLabel.setVisibility(View.VISIBLE);
|
||||
customText.setVisibility(View.GONE);
|
||||
protected void onBindDialogView(@NonNull View view) {
|
||||
Log.w(TAG, "onBindDialogView");
|
||||
super.onBindDialogView(view);
|
||||
|
||||
CustomDefaultPreference preference = (CustomDefaultPreference)getPreference();
|
||||
|
||||
this.spinner = (Spinner) view.findViewById(R.id.default_or_custom);
|
||||
this.defaultLabel = (TextView) view.findViewById(R.id.default_label);
|
||||
this.customText = (EditText) view.findViewById(R.id.custom_edit);
|
||||
|
||||
this.customText.setInputType(preference.inputType);
|
||||
this.customText.addTextChangedListener(new TextValidator());
|
||||
this.customText.setText(preference.getCustomValue());
|
||||
this.spinner.setOnItemSelectedListener(new SelectionLister());
|
||||
this.defaultLabel.setText(preference.getPrettyPrintValue(preference.defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
private class TextValidator implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
public Dialog onCreateDialog(Bundle instanceState) {
|
||||
Dialog dialog = super.onCreateDialog(instanceState);
|
||||
|
||||
CustomDefaultPreference preference = (CustomDefaultPreference)getPreference();
|
||||
|
||||
if (preference.isCustom()) spinner.setSelection(1, true);
|
||||
else spinner.setSelection(0, true);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
CustomDefaultPreference preference = (CustomDefaultPreference)getPreference();
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (spinner.getSelectedItemPosition() == 1) {
|
||||
positiveButton.setEnabled(validator.isValid(s.toString()));
|
||||
if (positiveResult) {
|
||||
if (spinner != null) preference.setCustom(spinner.getSelectedItemPosition() == 1);
|
||||
if (customText != null) preference.setCustomValue(customText.getText().toString());
|
||||
|
||||
preference.setSummary(preference.getSummary());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected interface CustomPreferenceValidator {
|
||||
public boolean isValid(String value);
|
||||
}
|
||||
|
||||
private static class NullValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
return true;
|
||||
interface CustomPreferenceValidator {
|
||||
public boolean isValid(String value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UriValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
if (TextUtils.isEmpty(value)) return true;
|
||||
|
||||
try {
|
||||
new URI(value);
|
||||
private static class NullValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
return true;
|
||||
} catch (URISyntaxException mue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HostnameValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
if (TextUtils.isEmpty(value)) return true;
|
||||
private class TextValidator implements TextWatcher {
|
||||
|
||||
try {
|
||||
URI uri = new URI(null, value, null, null);
|
||||
return true;
|
||||
} catch (URISyntaxException mue) {
|
||||
return false;
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
CustomDefaultPreference preference = (CustomDefaultPreference)getPreference();
|
||||
|
||||
if (spinner.getSelectedItemPosition() == 1) {
|
||||
Button positiveButton = ((AlertDialog)getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
positiveButton.setEnabled(preference.validator.isValid(s.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PortValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
public static class UriValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
if (TextUtils.isEmpty(value)) return true;
|
||||
|
||||
try {
|
||||
new URI(value);
|
||||
return true;
|
||||
} catch (URISyntaxException mue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HostnameValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
if (TextUtils.isEmpty(value)) return true;
|
||||
|
||||
try {
|
||||
URI uri = new URI(null, value, null, null);
|
||||
return true;
|
||||
} catch (URISyntaxException mue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PortValidator implements CustomPreferenceValidator {
|
||||
@Override
|
||||
public boolean isValid(String value) {
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SelectionLister implements AdapterView.OnItemSelectedListener {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
CustomDefaultPreference preference = (CustomDefaultPreference)getPreference();
|
||||
Button positiveButton = ((AlertDialog)getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
defaultLabel.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
|
||||
customText.setVisibility(position == 0 ? View.GONE : View.VISIBLE);
|
||||
positiveButton.setEnabled(position == 0 || preference.validator.isValid(customText.getText().toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
defaultLabel.setVisibility(View.VISIBLE);
|
||||
customText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
@ -7,13 +7,12 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -35,7 +34,7 @@ import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedE
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AdvancedPreferenceFragment extends PreferenceFragment {
|
||||
public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
private static final String TAG = AdvancedPreferenceFragment.class.getSimpleName();
|
||||
|
||||
private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging";
|
||||
@ -49,7 +48,6 @@ public class AdvancedPreferenceFragment extends PreferenceFragment {
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
masterSecret = getArguments().getParcelable("master_secret");
|
||||
addPreferencesFromResource(R.xml.preferences_advanced);
|
||||
|
||||
initializeIdentitySelection();
|
||||
|
||||
@ -58,6 +56,11 @@ public class AdvancedPreferenceFragment extends PreferenceFragment {
|
||||
submitDebugLog.setSummary(getVersion(getActivity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.preferences;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
@ -36,4 +35,6 @@ public class AdvancedRingtonePreference extends RingtonePreference {
|
||||
public void setCurrentRingtone(Uri uri) {
|
||||
currentRingtone = uri;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.doomonafireball.betterpickers.hmspicker.HmsPickerBuilder;
|
||||
@ -37,7 +37,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_app_protection);
|
||||
|
||||
masterSecret = getArguments().getParcelable("master_secret");
|
||||
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
|
||||
@ -52,6 +51,11 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_app_protection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@ -65,7 +69,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
|
||||
private void initializePlatformSpecificOptions() {
|
||||
PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
Preference screenSecurityPreference = findPreference(TextSecurePreferences.SCREEN_SECURITY_PREF);
|
||||
Preference screenSecurityPreference = findPreference(TextSecurePreferences.SCREEN_SECURITY_PREF);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
|
||||
screenSecurityPreference != null) {
|
||||
|
@ -2,7 +2,8 @@ package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -15,7 +16,6 @@ public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_appearance);
|
||||
|
||||
this.findPreference(TextSecurePreferences.THEME_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
this.findPreference(TextSecurePreferences.LANGUAGE_PREF).setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
@ -23,6 +23,11 @@ public class AppearancePreferenceFragment extends ListSummaryPreferenceFragment
|
||||
initializeListSummary((ListPreference)findPreference(TextSecurePreferences.LANGUAGE_PREF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_appearance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
@ -3,15 +3,13 @@ package org.thoughtcrime.securesms.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -28,7 +26,6 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_chats);
|
||||
|
||||
findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF)
|
||||
.setOnPreferenceChangeListener(new MediaDownloadChangeListener());
|
||||
@ -47,6 +44,11 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.MESSAGE_BODY_TEXT_SIZE_PREF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_chats);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@ -99,7 +101,7 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaDownloadChangeListener implements OnPreferenceChangeListener {
|
||||
private class MediaDownloadChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
Log.w(TAG, "onPreferenceChange");
|
||||
|
@ -0,0 +1,252 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.res.TypedArrayUtils;
|
||||
import android.support.v7.preference.DialogPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.takisoft.colorpicker.ColorPickerDialog;
|
||||
import com.takisoft.colorpicker.ColorPickerDialog.Size;
|
||||
import com.takisoft.colorpicker.ColorStateDrawable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class ColorPickerPreference extends DialogPreference {
|
||||
|
||||
private static final String TAG = ColorPickerPreference.class.getSimpleName();
|
||||
|
||||
private int[] colors;
|
||||
private CharSequence[] colorDescriptions;
|
||||
private int color;
|
||||
private int columns;
|
||||
private int size;
|
||||
private boolean sortColors;
|
||||
|
||||
private ImageView colorWidget;
|
||||
private OnPreferenceChangeListener listener;
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference, defStyleAttr, 0);
|
||||
|
||||
int colorsId = a.getResourceId(R.styleable.ColorPickerPreference_colors, R.array.color_picker_default_colors);
|
||||
|
||||
if (colorsId != 0) {
|
||||
colors = context.getResources().getIntArray(colorsId);
|
||||
}
|
||||
|
||||
colorDescriptions = a.getTextArray(R.styleable.ColorPickerPreference_colorDescriptions);
|
||||
color = a.getColor(R.styleable.ColorPickerPreference_currentColor, 0);
|
||||
columns = a.getInt(R.styleable.ColorPickerPreference_columns, 4);
|
||||
size = a.getInt(R.styleable.ColorPickerPreference_colorSize, 2);
|
||||
sortColors = a.getBoolean(R.styleable.ColorPickerPreference_sortColors, false);
|
||||
|
||||
a.recycle();
|
||||
|
||||
setWidgetLayoutResource(R.layout.preference_widget_color_swatch);
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
|
||||
android.R.attr.dialogPreferenceStyle));
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnPreferenceChangeListener(OnPreferenceChangeListener listener) {
|
||||
super.setOnPreferenceChangeListener(listener);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
colorWidget = (ImageView) holder.findViewById(R.id.color_picker_widget);
|
||||
setColorOnWidget(color);
|
||||
}
|
||||
|
||||
private void setColorOnWidget(int color) {
|
||||
if (colorWidget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Drawable[] colorDrawable = new Drawable[]
|
||||
{ContextCompat.getDrawable(getContext(), R.drawable.colorpickerpreference_pref_swatch)};
|
||||
colorWidget.setImageDrawable(new ColorStateDrawable(colorDrawable, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current color.
|
||||
*
|
||||
* @return The current color.
|
||||
*/
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current color.
|
||||
*
|
||||
* @param color The current color.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
setInternalColor(color, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the available colors.
|
||||
*
|
||||
* @return The available colors.
|
||||
*/
|
||||
public int[] getColors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available colors.
|
||||
*
|
||||
* @param colors The available colors.
|
||||
*/
|
||||
public void setColors(int[] colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the available colors should be sorted automatically based on their HSV
|
||||
* values.
|
||||
*
|
||||
* @return Whether the available colors should be sorted automatically based on their HSV
|
||||
* values.
|
||||
*/
|
||||
public boolean isSortColors() {
|
||||
return sortColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the available colors should be sorted automatically based on their HSV
|
||||
* values. The sorting does not modify the order of the original colors supplied via
|
||||
* {@link #setColors(int[])} or the XML attribute {@code app:colors}.
|
||||
*
|
||||
* @param sortColors Whether the available colors should be sorted automatically based on their
|
||||
* HSV values.
|
||||
*/
|
||||
public void setSortColors(boolean sortColors) {
|
||||
this.sortColors = sortColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available colors' descriptions that can be used by accessibility services.
|
||||
*
|
||||
* @return The available colors' descriptions.
|
||||
*/
|
||||
public CharSequence[] getColorDescriptions() {
|
||||
return colorDescriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available colors' descriptions that can be used by accessibility services.
|
||||
*
|
||||
* @param colorDescriptions The available colors' descriptions.
|
||||
*/
|
||||
public void setColorDescriptions(CharSequence[] colorDescriptions) {
|
||||
this.colorDescriptions = colorDescriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of columns to be used in the picker dialog for displaying the available
|
||||
* colors. If the value is less than or equals to 0, the number of columns will be determined
|
||||
* automatically by the system using FlexboxLayoutManager.
|
||||
*
|
||||
* @return The number of columns to be used in the picker dialog.
|
||||
* @see com.google.android.flexbox.FlexboxLayoutManager
|
||||
*/
|
||||
public int getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of columns to be used in the picker dialog for displaying the available
|
||||
* colors. If the value is less than or equals to 0, the number of columns will be determined
|
||||
* automatically by the system using FlexboxLayoutManager.
|
||||
*
|
||||
* @param columns The number of columns to be used in the picker dialog. Use 0 to set it to
|
||||
* 'auto' mode.
|
||||
* @see com.google.android.flexbox.FlexboxLayoutManager
|
||||
*/
|
||||
public void setColumns(int columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
*
|
||||
* @return The size of the color swatches in the dialog.
|
||||
* @see ColorPickerDialog#SIZE_SMALL
|
||||
* @see ColorPickerDialog#SIZE_LARGE
|
||||
*/
|
||||
@Size
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
*
|
||||
* @param size The size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
* @see ColorPickerDialog#SIZE_SMALL
|
||||
* @see ColorPickerDialog#SIZE_LARGE
|
||||
*/
|
||||
public void setSize(@Size int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
private void setInternalColor(int color, boolean force) {
|
||||
int oldColor = getPersistedInt(0);
|
||||
|
||||
boolean changed = oldColor != color;
|
||||
|
||||
if (changed || force) {
|
||||
this.color = color;
|
||||
|
||||
persistInt(color);
|
||||
|
||||
setColorOnWidget(color);
|
||||
|
||||
if (listener != null) listener.onPreferenceChange(this, color);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
setInternalColor(restoreValue ? getPersistedInt(0) : (!TextUtils.isEmpty(defaultValue) ? Color.parseColor(defaultValue) : 0), true);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import com.takisoft.colorpicker.ColorPickerDialog;
|
||||
import com.takisoft.colorpicker.OnColorSelectedListener;
|
||||
|
||||
public class ColorPickerPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements OnColorSelectedListener {
|
||||
|
||||
private int pickedColor;
|
||||
|
||||
public static ColorPickerPreferenceDialogFragmentCompat newInstance(String key) {
|
||||
ColorPickerPreferenceDialogFragmentCompat fragment = new ColorPickerPreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ColorPickerPreference pref = getColorPickerPreference();
|
||||
|
||||
ColorPickerDialog.Params params = new ColorPickerDialog.Params.Builder(getContext())
|
||||
.setSelectedColor(pref.getColor())
|
||||
.setColors(pref.getColors())
|
||||
.setColorContentDescriptions(pref.getColorDescriptions())
|
||||
.setSize(pref.getSize())
|
||||
.setSortColors(pref.isSortColors())
|
||||
.setColumns(pref.getColumns())
|
||||
.build();
|
||||
|
||||
ColorPickerDialog dialog = new ColorPickerDialog(getActivity(), this, params);
|
||||
dialog.setTitle(pref.getDialogTitle());
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
ColorPickerPreference preference = getColorPickerPreference();
|
||||
|
||||
if (positiveResult) {
|
||||
preference.setColor(pickedColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorSelected(int color) {
|
||||
this.pickedColor = color;
|
||||
|
||||
super.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
|
||||
ColorPickerPreference getColorPickerPreference() {
|
||||
return (ColorPickerPreference) getPreference();
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.GridLayout;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* A preference that allows the user to choose an application or shortcut.
|
||||
*/
|
||||
public class ColorPreference extends Preference {
|
||||
private int[] mColorChoices = {};
|
||||
private int mValue = 0;
|
||||
private int mItemLayoutId = R.layout.color_preference_item;
|
||||
private int mNumColumns = 5;
|
||||
private View mPreviewView;
|
||||
|
||||
public ColorPreference(Context context) {
|
||||
super(context);
|
||||
initAttrs(null, 0);
|
||||
}
|
||||
|
||||
public ColorPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initAttrs(attrs, 0);
|
||||
}
|
||||
|
||||
public ColorPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initAttrs(attrs, defStyle);
|
||||
}
|
||||
|
||||
private void initAttrs(AttributeSet attrs, int defStyle) {
|
||||
TypedArray a = getContext().getTheme().obtainStyledAttributes(
|
||||
attrs, R.styleable.ColorPreference, defStyle, defStyle);
|
||||
|
||||
try {
|
||||
mItemLayoutId = a.getResourceId(R.styleable.ColorPreference_itemLayout, mItemLayoutId);
|
||||
mNumColumns = a.getInteger(R.styleable.ColorPreference_numColumns, mNumColumns);
|
||||
// int choicesResId = a.getResourceId(R.styleable.ColorPreference_choices,
|
||||
// R.array.default_color_choice_values);
|
||||
// if (choicesResId > 0) {
|
||||
// String[] choices = a.getResources().getStringArray(choicesResId);
|
||||
// mColorChoices = new int[choices.length];
|
||||
// for (int i = 0; i < choices.length; i++) {
|
||||
// mColorChoices[i] = Color.parseColor(choices[i]);
|
||||
// }
|
||||
// }
|
||||
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
setWidgetLayoutResource(mItemLayoutId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
mPreviewView = view.findViewById(R.id.color_view);
|
||||
setColorViewValue(mPreviewView, mValue, false);
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
if (callChangeListener(value)) {
|
||||
mValue = value;
|
||||
persistInt(value);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setChoices(int[] values) {
|
||||
mColorChoices = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
super.onClick();
|
||||
|
||||
ColorDialogFragment fragment = ColorDialogFragment.newInstance();
|
||||
fragment.setPreference(this);
|
||||
|
||||
((AppCompatActivity) getContext()).getSupportFragmentManager().beginTransaction()
|
||||
.add(fragment, getFragmentTag())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToActivity() {
|
||||
super.onAttachedToActivity();
|
||||
|
||||
AppCompatActivity activity = (AppCompatActivity) getContext();
|
||||
ColorDialogFragment fragment = (ColorDialogFragment) activity
|
||||
.getSupportFragmentManager().findFragmentByTag(getFragmentTag());
|
||||
if (fragment != null) {
|
||||
// re-bind preference to fragment
|
||||
fragment.setPreference(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getInt(index, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
|
||||
setValue(restoreValue ? getPersistedInt(0) : (Integer) defaultValue);
|
||||
}
|
||||
|
||||
public String getFragmentTag() {
|
||||
return "color_" + getKey();
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public static class ColorDialogFragment extends android.support.v4.app.DialogFragment {
|
||||
private ColorPreference mPreference;
|
||||
private GridLayout mColorGrid;
|
||||
|
||||
public ColorDialogFragment() {
|
||||
}
|
||||
|
||||
public static ColorDialogFragment newInstance() {
|
||||
return new ColorDialogFragment();
|
||||
}
|
||||
|
||||
public void setPreference(ColorPreference preference) {
|
||||
mPreference = preference;
|
||||
repopulateItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
repopulateItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
|
||||
View rootView = layoutInflater.inflate(R.layout.color_preference_items, null);
|
||||
|
||||
mColorGrid = (GridLayout) rootView.findViewById(R.id.color_grid);
|
||||
mColorGrid.setColumnCount(mPreference.mNumColumns);
|
||||
repopulateItems();
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setView(rootView)
|
||||
.create();
|
||||
}
|
||||
|
||||
private void repopulateItems() {
|
||||
if (mPreference == null || mColorGrid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = mColorGrid.getContext();
|
||||
mColorGrid.removeAllViews();
|
||||
for (final int color : mPreference.mColorChoices) {
|
||||
View itemView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.color_preference_item, mColorGrid, false);
|
||||
|
||||
setColorViewValue(itemView.findViewById(R.id.color_view), color,
|
||||
color == mPreference.getValue());
|
||||
itemView.setClickable(true);
|
||||
itemView.setFocusable(true);
|
||||
itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mPreference.setValue(color);
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
mColorGrid.addView(itemView);
|
||||
}
|
||||
|
||||
sizeDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
sizeDialog();
|
||||
}
|
||||
|
||||
private void sizeDialog() {
|
||||
// if (mPreference == null || mColorGrid == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// Dialog dialog = getDialog();
|
||||
// if (dialog == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final Resources res = mColorGrid.getContext().getResources();
|
||||
// DisplayMetrics dm = res.getDisplayMetrics();
|
||||
//
|
||||
// // Can't use Integer.MAX_VALUE here (weird issue observed otherwise on 4.2)
|
||||
// mColorGrid.measure(
|
||||
// View.MeasureSpec.makeMeasureSpec(dm.widthPixels, View.MeasureSpec.AT_MOST),
|
||||
// View.MeasureSpec.makeMeasureSpec(dm.heightPixels, View.MeasureSpec.AT_MOST));
|
||||
// int width = mColorGrid.getMeasuredWidth();
|
||||
// int height = mColorGrid.getMeasuredHeight();
|
||||
//
|
||||
// int extraPadding = res.getDimensionPixelSize(R.dimen.color_grid_extra_padding);
|
||||
//
|
||||
// width += extraPadding;
|
||||
// height += extraPadding;
|
||||
//
|
||||
// dialog.getWindow().setLayout(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setColorViewValue(View view, int color, boolean selected) {
|
||||
if (view instanceof ImageView) {
|
||||
ImageView imageView = (ImageView) view;
|
||||
Resources res = imageView.getContext().getResources();
|
||||
|
||||
Drawable currentDrawable = imageView.getDrawable();
|
||||
GradientDrawable colorChoiceDrawable;
|
||||
if (currentDrawable instanceof GradientDrawable) {
|
||||
// Reuse drawable
|
||||
colorChoiceDrawable = (GradientDrawable) currentDrawable;
|
||||
} else {
|
||||
colorChoiceDrawable = new GradientDrawable();
|
||||
colorChoiceDrawable.setShape(GradientDrawable.OVAL);
|
||||
}
|
||||
|
||||
// Set stroke to dark version of color
|
||||
// int darkenedColor = Color.rgb(
|
||||
// Color.red(color) * 192 / 256,
|
||||
// Color.green(color) * 192 / 256,
|
||||
// Color.blue(color) * 192 / 256);
|
||||
|
||||
colorChoiceDrawable.setColor(color);
|
||||
// colorChoiceDrawable.setStroke((int) TypedValue.applyDimension(
|
||||
// TypedValue.COMPLEX_UNIT_DIP, 2, res.getDisplayMetrics()), darkenedColor);
|
||||
|
||||
Drawable drawable = colorChoiceDrawable;
|
||||
if (selected) {
|
||||
BitmapDrawable checkmark = (BitmapDrawable) res.getDrawable(R.drawable.check);
|
||||
checkmark.setGravity(Gravity.CENTER);
|
||||
drawable = new LayerDrawable(new Drawable[]{
|
||||
colorChoiceDrawable,
|
||||
checkmark});
|
||||
}
|
||||
|
||||
imageView.setImageDrawable(drawable);
|
||||
|
||||
} else if (view instanceof TextView) {
|
||||
((TextView) view).setTextColor(color);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,19 @@ package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.view.View;
|
||||
|
||||
public class CorrectedPreferenceFragment extends PreferenceFragment {
|
||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
||||
|
||||
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
@ -15,4 +24,25 @@ public class CorrectedPreferenceFragment extends PreferenceFragment {
|
||||
if (lv != null) lv.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
DialogFragment dialogFragment = null;
|
||||
|
||||
if (preference instanceof RingtonePreference) {
|
||||
dialogFragment = RingtonePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
} else if (preference instanceof ColorPickerPreference) {
|
||||
dialogFragment = ColorPickerPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomDefaultPreference) {
|
||||
dialogFragment = CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
}
|
||||
|
||||
if (dialogFragment != null) {
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -18,11 +18,10 @@ package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.preference.ListPreference;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -69,8 +68,8 @@ public class LEDColorListPreference extends ListPreference {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
this.colorImageView = (ImageView)view.findViewById(R.id.color_view);
|
||||
setPreviewColor(getValue());
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.ListPreference;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
* List preference for LED blink pattern notification.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class LedBlinkPatternListPreference extends SignalListPreference implements OnSeekBarChangeListener {
|
||||
|
||||
private Context context;
|
||||
private SeekBar seekBarOn;
|
||||
private SeekBar seekBarOff;
|
||||
|
||||
private TextView seekBarOnLabel;
|
||||
private TextView seekBarOffLabel;
|
||||
|
||||
private boolean dialogInProgress;
|
||||
|
||||
public LedBlinkPatternListPreference(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public LedBlinkPatternListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
if (positiveResult) {
|
||||
String blinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
|
||||
if (blinkPattern.equals("custom")) showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeSeekBarValues() {
|
||||
String patternString = TextSecurePreferences.getNotificationLedPatternCustom(context);
|
||||
String[] patternArray = patternString.split(",");
|
||||
seekBarOn.setProgress(Integer.parseInt(patternArray[0]));
|
||||
seekBarOff.setProgress(Integer.parseInt(patternArray[1]));
|
||||
}
|
||||
|
||||
private void initializeDialog(View view) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setIconAttribute(R.attr.dialog_info_icon);
|
||||
builder.setTitle(R.string.preferences__pref_led_blink_custom_pattern_title);
|
||||
builder.setView(view);
|
||||
builder.setOnCancelListener(new CustomDialogCancelListener());
|
||||
builder.setNegativeButton(android.R.string.cancel, new CustomDialogCancelListener());
|
||||
builder.setPositiveButton(android.R.string.ok, new CustomDialogClickListener());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showDialog() {
|
||||
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.led_pattern_dialog, null);
|
||||
|
||||
this.seekBarOn = (SeekBar)view.findViewById(R.id.SeekBarOn);
|
||||
this.seekBarOff = (SeekBar)view.findViewById(R.id.SeekBarOff);
|
||||
this.seekBarOnLabel = (TextView)view.findViewById(R.id.SeekBarOnMsLabel);
|
||||
this.seekBarOffLabel = (TextView)view.findViewById(R.id.SeekBarOffMsLabel);
|
||||
|
||||
this.seekBarOn.setOnSeekBarChangeListener(this);
|
||||
this.seekBarOff.setOnSeekBarChangeListener(this);
|
||||
|
||||
initializeSeekBarValues();
|
||||
initializeDialog(view);
|
||||
|
||||
dialogInProgress = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Parcelable state) {
|
||||
super.onRestoreInstanceState(state);
|
||||
if (dialogInProgress) {
|
||||
showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
dialogInProgress = false;
|
||||
return super.onCreateDialogView();
|
||||
}
|
||||
|
||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromTouch) {
|
||||
if (seekbar.equals(seekBarOn)) {
|
||||
seekBarOnLabel.setText(Integer.toString(progress));
|
||||
} else if (seekbar.equals(seekBarOff)) {
|
||||
seekBarOffLabel.setText(Integer.toString(progress));
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
private class CustomDialogCancelListener implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialogInProgress = false;
|
||||
}
|
||||
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialogInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomDialogClickListener implements DialogInterface.OnClickListener {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String pattern = seekBarOnLabel.getText() + "," + seekBarOffLabel.getText();
|
||||
dialogInProgress = false;
|
||||
|
||||
TextSecurePreferences.setNotificationLedPatternCustom(context, pattern);
|
||||
Toast.makeText(context, R.string.preferences__pref_led_blink_custom_pattern_set, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
|
@ -19,7 +19,7 @@ package org.thoughtcrime.securesms.preferences;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
@ -33,19 +33,23 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class MmsPreferencesFragment extends PreferenceFragment {
|
||||
public class MmsPreferencesFragment extends CorrectedPreferenceFragment {
|
||||
|
||||
private static final String TAG = MmsPreferencesFragment.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_manual_mms);
|
||||
|
||||
((PassphraseRequiredActionBarActivity) getActivity()).getSupportActionBar()
|
||||
.setTitle(R.string.preferences__advanced_mms_access_point_names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_manual_mms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@ -74,15 +78,15 @@ public class MmsPreferencesFragment extends PreferenceFragment {
|
||||
@Override
|
||||
protected void onPostExecute(LegacyMmsConnection.Apn apnDefaults) {
|
||||
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_HOST_PREF))
|
||||
.setValidator(new CustomDefaultPreference.UriValidator())
|
||||
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.UriValidator())
|
||||
.setDefaultValue(apnDefaults.getMmsc());
|
||||
|
||||
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_PROXY_HOST_PREF))
|
||||
.setValidator(new CustomDefaultPreference.HostnameValidator())
|
||||
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.HostnameValidator())
|
||||
.setDefaultValue(apnDefaults.getProxy());
|
||||
|
||||
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_PROXY_PORT_PREF))
|
||||
.setValidator(new CustomDefaultPreference.PortValidator())
|
||||
.setValidator(new CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.PortValidator())
|
||||
.setDefaultValue(apnDefaults.getPort());
|
||||
|
||||
((CustomDefaultPreference)findPreference(TextSecurePreferences.MMSC_USERNAME_PREF))
|
||||
|
@ -7,11 +7,12 @@ import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -21,13 +22,14 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName();
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
masterSecret = getArguments().getParcelable("master_secret");
|
||||
addPreferencesFromResource(R.xml.preferences_notifications);
|
||||
|
||||
this.findPreference(TextSecurePreferences.LED_COLOR_PREF)
|
||||
.setOnPreferenceChangeListener(new ListSummaryListener());
|
||||
@ -47,7 +49,12 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.REPEAT_ALERTS_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF));
|
||||
initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF));
|
||||
initializeRingtoneSummary((RingtonePreference) findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
initializeRingtoneSummary((AdvancedRingtonePreference) findPreference(TextSecurePreferences.RINGTONE_PREF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_notifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,12 +66,12 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
private class RingtoneSummaryListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String value = (String) newValue;
|
||||
Uri value = (Uri) newValue;
|
||||
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
if (value == null) {
|
||||
preference.setSummary(R.string.preferences__silent);
|
||||
} else {
|
||||
Ringtone tone = RingtoneManager.getRingtone(getActivity(), Uri.parse(value));
|
||||
Ringtone tone = RingtoneManager.getRingtone(getActivity(), value);
|
||||
if (tone != null) {
|
||||
preference.setSummary(tone.getTitle(getActivity()));
|
||||
}
|
||||
@ -74,12 +81,13 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeRingtoneSummary(RingtonePreference pref) {
|
||||
RingtoneSummaryListener listener =
|
||||
(RingtoneSummaryListener) pref.getOnPreferenceChangeListener();
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
private void initializeRingtoneSummary(AdvancedRingtonePreference pref) {
|
||||
RingtoneSummaryListener listener = (RingtoneSummaryListener) pref.getOnPreferenceChangeListener();
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
String encodedUri = sharedPreferences.getString(pref.getKey(), null);
|
||||
Uri uri = !TextUtils.isEmpty(encodedUri) ? Uri.parse(encodedUri) : null;
|
||||
|
||||
listener.onPreferenceChange(pref, sharedPreferences.getString(pref.getKey(), ""));
|
||||
listener.onPreferenceChange(pref, uri);
|
||||
}
|
||||
|
||||
public static CharSequence getSummary(Context context) {
|
||||
|
@ -5,10 +5,11 @@ import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.Preference;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@ -16,7 +17,6 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
@ -55,12 +55,11 @@ public class ProfilePreference extends Preference {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
|
||||
avatarView = ViewUtil.findById(view, R.id.avatar);
|
||||
profileNameView = ViewUtil.findById(view, R.id.profile_name);
|
||||
profileNumberView = ViewUtil.findById(view, R.id.number);
|
||||
public void onBindViewHolder(PreferenceViewHolder viewHolder) {
|
||||
super.onBindViewHolder(viewHolder);
|
||||
avatarView = (ImageView)viewHolder.findViewById(R.id.avatar);
|
||||
profileNameView = (TextView)viewHolder.findViewById(R.id.profile_name);
|
||||
profileNumberView = (TextView)viewHolder.findViewById(R.id.number);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
@ -0,0 +1,464 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
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.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,583 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
|
||||
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.CursorAdapter;
|
||||
import android.widget.HeaderViewListAdapter;
|
||||
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.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 Cursor cursor;
|
||||
|
||||
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();
|
||||
|
||||
createCursor(ringtonePreference.getRingtone());
|
||||
|
||||
String colTitle = cursor.getColumnName(RingtoneManager.TITLE_COLUMN_INDEX);
|
||||
|
||||
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(cursor, selectedIndex, colTitle, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
if (i < cursor.getCount()) {
|
||||
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 Cursor createCursor(Uri ringtoneUri) {
|
||||
RingtonePreference ringtonePreference = getRingtonePreference();
|
||||
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};
|
||||
return this.cursor = new MergeCursor(cursors);
|
||||
}
|
||||
|
||||
@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, Cursor> installTask = new AsyncTask<Uri, Void, Cursor>() {
|
||||
@Override
|
||||
protected Cursor doInBackground(Uri... params) {
|
||||
try {
|
||||
Uri newUri = addCustomExternalRingtone(context, params[0], ringtoneType);
|
||||
|
||||
return createCursor(newUri);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Log.e(TAG, "Unable to add new ringtone: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Cursor newCursor) {
|
||||
if (newCursor != null) {
|
||||
final ListView listView = ((AlertDialog) getDialog()).getListView();
|
||||
final CursorAdapter adapter = ((CursorAdapter) ((HeaderViewListAdapter) listView.getAdapter()).getWrappedAdapter());
|
||||
adapter.changeCursor(newCursor);
|
||||
|
||||
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.execute(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();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
@ -45,8 +46,8 @@ public class SignalListPreference extends ListPreference {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
|
||||
this.rightSummary = (TextView)view.findViewById(R.id.right_summary);
|
||||
setSummary(this.summary);
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
@ -42,8 +43,8 @@ public class SignalRingtonePreference extends AdvancedRingtonePreference {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
|
||||
this.rightSummary = (TextView)view.findViewById(R.id.right_summary);
|
||||
setSummary(summary);
|
||||
|
@ -6,14 +6,14 @@ import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Telephony;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@ -27,7 +27,7 @@ public class SmsMmsPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_sms_mms);
|
||||
|
||||
|
||||
this.findPreference(MMS_PREF)
|
||||
.setOnPreferenceClickListener(new ApnPreferencesClickListener());
|
||||
@ -35,6 +35,11 @@ public class SmsMmsPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
initializePlatformSpecificOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_sms_mms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
Loading…
Reference in New Issue
Block a user