Fixed Audio Fade, Crossfade & Android Auto search

main
Prathamesh More 2021-09-18 14:10:22 +05:30
parent 7f683eb0ee
commit ee7545f64e
15 changed files with 269 additions and 79 deletions

View File

@ -6,7 +6,6 @@ apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 31
buildToolsVersion = '29.0.3'
defaultConfig {
minSdkVersion 21
@ -33,6 +32,8 @@ android {
buildTypes {
release {
//debuggable true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
@ -104,7 +105,7 @@ dependencies {
//WebServer by NanoHttpd
implementation "org.nanohttpd:nanohttpd:2.3.1"
def nav_version = '2.4.0-alpha09'
def nav_version = "2.4.0-alpha09"
implementation "androidx.navigation:navigation-runtime-ktx:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
@ -158,6 +159,7 @@ dependencies {
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
implementation 'cat.ereza:customactivityoncrash:2.3.0'
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

View File

@ -265,7 +265,7 @@
android:value="true" />
<!-- Android Auto -->
<!-- <meta-data
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
<meta-data
@ -274,7 +274,7 @@
<meta-data
android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification"/>
-->
<!-- ChromeCast -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"

View File

@ -32,6 +32,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
@ -110,8 +111,6 @@ class BlurPlayerFragment : AbsPlayerFragment(R.layout.fragment_blur),
get() = lastColor
private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -33,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil.blurAmount
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import org.koin.android.ext.android.bind
@ -136,8 +137,6 @@ class CardBlurFragment : AbsPlayerFragment(R.layout.fragment_card_blur_player),
}
private fun updateBlur() {
val blurAmount = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getInt(NEW_BLUR_AMOUNT, 25)
binding.colorBackground.clearColorFilter()
GlideApp.with(requireActivity()).asBitmapPalette()
.songCoverOptions(MusicPlayerRemote.currentSong)

View File

@ -25,6 +25,7 @@ import android.os.IBinder
import android.provider.DocumentsContract
import android.widget.Toast
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.MusicService
@ -300,7 +301,7 @@ object MusicPlayerRemote : KoinComponent {
return false
}
fun setShuffleMode(shuffleMode: Int): Boolean {
private fun setShuffleMode(shuffleMode: Int): Boolean {
if (musicService != null) {
musicService!!.shuffleMode = shuffleMode
return true
@ -319,7 +320,7 @@ object MusicPlayerRemote : KoinComponent {
}
Toast.makeText(
musicService,
musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue),
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
Toast.LENGTH_SHORT
).show()
return true
@ -335,8 +336,8 @@ object MusicPlayerRemote : KoinComponent {
openQueue(songs, 0, false)
}
val toast =
if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue,
if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
R.string.added_x_titles_to_playing_queue,
songs.size
)
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
@ -356,7 +357,7 @@ object MusicPlayerRemote : KoinComponent {
}
Toast.makeText(
musicService,
musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue),
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
Toast.LENGTH_SHORT
).show()
return true
@ -372,8 +373,8 @@ object MusicPlayerRemote : KoinComponent {
openQueue(songs, 0, false)
}
val toast =
if (songs.size == 1) musicService!!.resources.getString(code.name.monkey.retromusic.R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
code.name.monkey.retromusic.R.string.added_x_titles_to_playing_queue,
if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
R.string.added_x_titles_to_playing_queue,
songs.size
)
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()

View File

@ -1,50 +1,66 @@
package code.name.monkey.retromusic.service
import android.animation.Animator
import android.animation.ValueAnimator
import android.media.MediaPlayer
import androidx.core.animation.doOnEnd
import code.name.monkey.retromusic.service.playback.Playback
import java.util.*
import code.name.monkey.retromusic.util.PreferenceUtil
class AudioFader(
private val player: Playback,
durationMillis: Long,
private val fadeIn: Boolean,
private val doOnEnd: Runnable
) {
val timer = Timer()
var volume = if (fadeIn) 0F else 1F
val maxVolume = if (fadeIn) 1F else 0F
private val volumeStep: Float = PERIOD / durationMillis.toFloat()
class AudioFader {
companion object {
fun start() {
timer.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
setVolume()
if (volume < 0 || volume > 1) {
player.setVolume(maxVolume)
stop()
doOnEnd.run()
} else {
player.setVolume(volume)
}
@JvmStatic
inline fun createFadeAnimator(
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
mediaPlayer: MediaPlayer,
crossinline endAction: (animator: Animator) -> Unit /* Code to run when Animator Ends*/
): Animator? {
val duration = PreferenceUtil.crossFadeDuration * 1000
if (duration == 0) {
return null
}
val startValue = if (fadeIn) 0f else 1.0f
val endValue = if (fadeIn) 1.0f else 0f
return ValueAnimator.ofFloat(startValue, endValue).apply {
this.duration = duration.toLong()
addUpdateListener { animation: ValueAnimator ->
mediaPlayer.setVolume(
animation.animatedValue as Float, animation.animatedValue as Float
)
}
}, 0, PERIOD
)
}
doOnEnd {
endAction(it)
// Set end values
mediaPlayer.setVolume(endValue, endValue)
}
}
}
fun stop() {
timer.purge()
timer.cancel()
}
private fun setVolume() {
if (fadeIn) {
volume += volumeStep
} else {
volume -= volumeStep
@JvmStatic
fun startFadeAnimator(
playback: Playback,
fadeIn: Boolean /* fadeIn -> true fadeOut -> false*/,
callback: Runnable /* Code to run when Animator Ends*/
) {
val duration = PreferenceUtil.audioFadeDuration.toLong()
if (duration == 0L) {
callback.run()
return
}
val startValue = if (fadeIn) 0f else 1.0f
val endValue = if (fadeIn) 1.0f else 0f
val animator = ValueAnimator.ofFloat(startValue, endValue)
animator.duration = duration
animator.addUpdateListener { animation: ValueAnimator ->
playback.setVolume(
animation.animatedValue as Float
)
}
animator.doOnEnd {
callback.run()
}
animator.start()
}
}
companion object {
const val PERIOD = 100L
}
}

View File

@ -17,6 +17,7 @@ package code.name.monkey.retromusic.service
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.media.session.MediaSessionCompat
import code.name.monkey.retromusic.auto.AutoMediaIDHelper
import code.name.monkey.retromusic.helper.MusicPlayerRemote
@ -31,7 +32,6 @@ import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.MusicUtil
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
/**
@ -56,8 +56,7 @@ class MediaSessionCallback(
println(musicId)
val itemId = musicId?.toLong() ?: -1
val songs: ArrayList<Song> = ArrayList()
val category = AutoMediaIDHelper.extractCategory(mediaId)
when (category) {
when (val category = AutoMediaIDHelper.extractCategory(mediaId)) {
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> {
val album: Album = albumRepository.album(itemId)
songs.addAll(album.songs)
@ -111,6 +110,43 @@ class MediaSessionCallback(
musicService.play()
}
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
val songs = ArrayList<Song>()
if (query.isNullOrEmpty()) {
// The user provided generic string e.g. 'Play music'
// Build appropriate playlist queue
songs.addAll(songRepository.songs())
} else {
// Build a queue based on songs that match "query" or "extras" param
val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
if (artistQuery != null) {
artistRepository.artists(artistQuery).forEach {
songs.addAll(it.songs)
}
}
} else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
if (albumQuery != null) {
albumRepository.albums(albumQuery).forEach {
songs.addAll(it.songs)
}
}
}
}
if (songs.isEmpty()) {
// No focus found, search by query for song title
query?.also {
songs.addAll(songRepository.songs(it))
}
}
musicService.openQueue(songs, 0, true)
musicService.play()
}
override fun onPlay() {
super.onPlay()

View File

@ -21,6 +21,7 @@ import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@ -97,12 +98,14 @@ import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PackageValidator;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroUtil;
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
/**
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
*/
public class MusicService extends MediaBrowserServiceCompat
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks, OnAudioVolumeChangedListener {
public static final String TAG = MusicService.class.getSimpleName();
public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
@ -238,6 +241,7 @@ public class MusicService extends MediaBrowserServiceCompat
private List<Song> originalPlayingQueue = new ArrayList<>();
private List<Song> playingQueue = new ArrayList<>();
private boolean pausedByTransientLossOfFocus;
private AudioVolumeObserver audioVolumeObserver = null;
private final BroadcastReceiver becomingNoisyReceiver =
new BroadcastReceiver() {
@ -348,7 +352,6 @@ public class MusicService extends MediaBrowserServiceCompat
private ThrottledSeekHandler throttledSeekHandler;
private Handler uiThreadHandler;
private PowerManager.WakeLock wakeLock;
private AudioFader fader;
private static Bitmap copy(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig();
@ -446,6 +449,9 @@ public class MusicService extends MediaBrowserServiceCompat
.registerContentObserver(
MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
audioVolumeObserver = new AudioVolumeObserver(this);
audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this);
restoreState();
@ -491,6 +497,23 @@ public class MusicService extends MediaBrowserServiceCompat
wakeLock.acquire(milli);
}
boolean pausedByZeroVolume;
@Override
public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
if (PreferenceUtil.INSTANCE.isPauseOnZeroVolume()) {
if (isPlaying() && currentVolume < 1) {
pause();
System.out.println("Paused");
pausedByZeroVolume = true;
} else if (pausedByZeroVolume && currentVolume >= 1) {
System.out.println("Played");
play();
pausedByZeroVolume = false;
}
}
}
public void addSong(int position, Song song) {
playingQueue.add(position, song);
originalPlayingQueue.add(position, song);
@ -558,10 +581,10 @@ public class MusicService extends MediaBrowserServiceCompat
}
public Song getNextSong() {
if (!isLastTrack() || getRepeatMode() == REPEAT_MODE_THIS) {
return getSongAt(getNextPosition(false));
} else {
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
return null;
} else {
return getSongAt(getNextPosition(false));
}
}
@ -926,6 +949,13 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackEndedWithCrossfade() {
trackEndedByCrossfade = true;
acquireWakeLock(30000);
playerHandler.sendEmptyMessage(TRACK_ENDED);
}
@Override
public void onTrackWentToNext() {
playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
@ -981,16 +1011,11 @@ public class MusicService extends MediaBrowserServiceCompat
public void pause() {
pausedByTransientLossOfFocus = false;
if (playback != null && playback.isPlaying()) {
if (fader != null) {
fader.stop();
}
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
if (playback != null && playback.isPlaying()) {
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
}
startFadeAnimator(playback, false, () -> {
//Code to run when Animator Ends
playback.pause();
notifyChange(PLAY_STATE_CHANGED);
});
fader.start();
}
}
@ -1013,10 +1038,8 @@ public class MusicService extends MediaBrowserServiceCompat
if (MusicPlayerRemote.INSTANCE.isCasting()) {
return;
}
if (fader != null) {
fader.stop();
}
fader = new AudioFader(playback, PreferenceUtil.INSTANCE.getAudioFadeDuration(), false, () -> {
startFadeAnimator(playback, true, () -> {
// Code when Animator Ends
if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true;
@ -1031,9 +1054,9 @@ public class MusicService extends MediaBrowserServiceCompat
playerHandler.removeMessages(DUCK);
playerHandler.sendEmptyMessage(UNDUCK);
});
//Start Playback with Animator
playback.start();
notifyChange(PLAY_STATE_CHANGED);
fader.start();
}
}
} else {

View File

@ -51,5 +51,7 @@ interface Playback {
fun onTrackWentToNext()
fun onTrackEnded()
fun onTrackEndedWithCrossfade()
}
}

View File

@ -28,13 +28,22 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import code.name.monkey.retromusic.adapter.Storage;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
import code.name.monkey.retromusic.repository.SortedCursor;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
@ -258,4 +267,76 @@ public final class FileUtil {
return file.getAbsoluteFile();
}
}
// https://github.com/DrKLO/Telegram/blob/ab221dafadbc17459d78d9ea3e643ae18e934b16/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java#L939
public static ArrayList<Storage> listRoots() {
ArrayList<Storage> storageItems = new ArrayList<>();
HashSet<String> paths = new HashSet<>();
String defaultPath = Environment.getExternalStorageDirectory().getPath();
String defaultPathState = Environment.getExternalStorageState();
if (defaultPathState.equals(Environment.MEDIA_MOUNTED) || defaultPathState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
Storage ext = new Storage();
if (Environment.isExternalStorageRemovable()) {
ext.title = "SD Card";
} else {
ext.title = "Internal Storage";
}
ext.file = Environment.getExternalStorageDirectory();
storageItems.add(ext);
paths.add(defaultPath);
}
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
tokens.nextToken();
String path = tokens.nextToken();
if (paths.contains(path)) {
continue;
}
if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) {
if (!new File(path).isDirectory()) {
int index = path.lastIndexOf('/');
if (index != -1) {
String newPath = "/storage/" + path.substring(index + 1);
if (new File(newPath).isDirectory()) {
path = newPath;
}
}
}
paths.add(path);
try {
Storage item = new Storage();
if (path.toLowerCase().contains("sd")) {
item.title = "SD Card";
} else {
item.title = "External Storage";
}
item.file = new File(path);
storageItems.add(item);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return storageItems;
}
}

View File

@ -269,6 +269,8 @@ object PreferenceUtil {
BLURRED_ALBUM_ART, false
)
val blurAmount get() = sharedPreferences.getInt(NEW_BLUR_AMOUNT, 25)
val isCarouselEffect
get() = sharedPreferences.getBoolean(
CAROUSEL_EFFECT, false
@ -608,8 +610,7 @@ object PreferenceUtil {
val isWhiteList: Boolean
get() = sharedPreferences.getBoolean(WHITELIST_MUSIC, false)
var crossFadeDuration
val crossFadeDuration
get() = sharedPreferences
.getInt(CROSS_FADE_DURATION, 0)
set(value) = sharedPreferences.edit { putInt(CROSS_FADE_DURATION, value) }
}

View File

@ -21,6 +21,7 @@
android:defaultValue="0"
android:key="cross_fade_duration"
android:layout="@layout/list_item_view_seekbar"
app:ateKey_pref_unit="s"
android:max="12"
android:summary="@string/pref_summary_cross_fade"
android:title="@string/pref_title_cross_fade"

View File

@ -86,6 +86,7 @@
android:summary="@string/pref_blur_amount_summary"
android:title="@string/pref_blur_amount_title"
app:icon="@drawable/ic_blur_on"
app:ateKey_pref_unit="px"
app:showSeekBarValue="true" />
</code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceCategory>

View File

@ -1,10 +1,13 @@
package code.name.monkey.appthemehelper.common.prefs.supportv7
import android.content.Context
import android.text.Editable
import android.util.AttributeSet
import android.widget.SeekBar
import android.widget.TextView
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.widget.doAfterTextChanged
import androidx.preference.PreferenceViewHolder
import androidx.preference.SeekBarPreference
import code.name.monkey.appthemehelper.R
@ -19,7 +22,16 @@ class ATESeekBarPreference @JvmOverloads constructor(
defStyleRes: Int = -1
) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) {
var unit: String = ""
init {
val attributes =
context.obtainStyledAttributes(attrs, R.styleable.ATESeekBarPreference, 0, 0)
attributes.getString(R.styleable.ATESeekBarPreference_ateKey_pref_unit)?.let {
unit = it
}
attributes.recycle()
icon?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
ATHUtil.resolveColor(
context,
@ -32,5 +44,17 @@ class ATESeekBarPreference @JvmOverloads constructor(
super.onBindViewHolder(view)
val seekBar = view.findViewById(R.id.seekbar) as SeekBar
TintHelper.setTintAuto(seekBar, ThemeStore.accentColor(context), false)
(view.findViewById(R.id.seekbar_value) as TextView).apply {
appendUnit(editableText)
doAfterTextChanged {
appendUnit(it)
}
}
}
private fun TextView.appendUnit(editable: Editable?) {
if (!editable.toString().endsWith(unit)) {
append(unit)
}
}
}

4
appthemehelper/src/main/res/values/attrs.xml Executable file → Normal file
View File

@ -16,6 +16,10 @@
<attr name="ateKey_pref_color" format="string" />
</declare-styleable>
<declare-styleable name="ATESeekBarPreference">
<attr name="ateKey_pref_unit" format="string" />
</declare-styleable>
<declare-styleable name="ATEDialogPreference">
<attr name="ateKey_pref_dialog" format="string" />
</declare-styleable>