error page

This commit is contained in:
Li ZongYing
2024-04-26 19:57:12 +08:00
parent fe92fa5853
commit 5aa08f51a1
26 changed files with 339 additions and 107 deletions
@@ -1,8 +1,11 @@
package com.lizongying.mytv
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.animation.ScaleAnimation
import androidx.core.view.marginTop
@@ -12,6 +15,7 @@ import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.CardBinding
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlin.math.abs
class CardAdapter(
@@ -45,6 +49,7 @@ class CardAdapter(
binding.desc.layoutParams = layoutParams
val cardView = binding.root
cardView.isClickable = true
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView, binding)
@@ -55,14 +60,14 @@ class CardAdapter(
recyclerView.invalidate()
}
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) {
private fun startScaleAnimation(view: View) {
val scaleAnimation = ScaleAnimation(
fromScale, toScale,
fromScale, toScale,
0.9f, 1.0f,
0.9f, 1.0f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
)
scaleAnimation.duration = duration
scaleAnimation.duration = 200
scaleAnimation.fillAfter = false
view.startAnimation(scaleAnimation)
}
@@ -80,7 +85,7 @@ class CardAdapter(
focused = cardView
if (focusable) {
startScaleAnimation(cardView, 0.9f, 1.0f, 200)
startScaleAnimation(cardView)
viewHolder.focus(true)
}
} else {
@@ -94,6 +99,34 @@ class CardAdapter(
listener?.onItemClicked(item)
}
var downX = 0f
var downY = 0f
val touchSlop = ViewConfiguration.get(cardView.context).scaledTouchSlop
cardView.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
true
}
MotionEvent.ACTION_UP -> {
val upX = event.x
val upY = event.y
val deltaX = abs(upX - downX)
val deltaY = abs(upY - downY)
if (deltaX < touchSlop && deltaY < touchSlop) {
// 如果位移量小于阈值,则执行点击操作
cardView.performClick()
}
true
}
else -> false
}
}
cardView.setOnKeyListener { _, keyCode, event: KeyEvent? ->
if (event?.action == KeyEvent.ACTION_DOWN) {
return@setOnKeyListener listener?.onKey(keyCode) ?: false
@@ -132,6 +165,33 @@ class CardAdapter(
}
}
fun updateEPG() {
for (i in 0 until recyclerView.childCount) {
val childView = recyclerView.getChildAt(i)
val viewHolder = recyclerView.getChildViewHolder(childView) as ViewHolder
if (viewHolder.view.tag != null && viewHolder.view.tag is TVViewModel) {
val tvViewModel = viewHolder.view.tag as TVViewModel
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
val title = epg.last().title
if (viewHolder.binding.desc.text != title) {
viewHolder.binding.desc.text = title
Log.i(TAG, "updateEPG $title")
}
} else {
if (viewHolder.binding.desc.text != "") {
viewHolder.binding.desc.text = ""
Log.i(TAG, "updateEPG")
}
}
}
}
}
fun toPosition(position: Int) {
recyclerView.post {
recyclerView.scrollToPosition(position)
@@ -0,0 +1,53 @@
package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginLeft
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ErrorBinding
class ErrorFragment : Fragment() {
private var _binding: ErrorBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ErrorBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width)
binding.logo.layoutParams.height = application.px2Px(binding.logo.layoutParams.height)
val layoutParamsMain = binding.main.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsMain.leftMargin = application.px2Px(binding.main.marginLeft)
binding.main.layoutParams = layoutParamsMain
val layoutParams = binding.msg.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.msg.marginTop)
binding.msg.layoutParams = layoutParams
binding.msg.textSize = application.px2PxFont(binding.msg.textSize)
(activity as MainActivity).fragmentReady(TAG)
return binding.root
}
fun show(msg: String) {
binding.msg.text = msg
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "ErrorFragment"
}
}
@@ -52,6 +52,7 @@ class InfoFragment : Fragment() {
binding.logo.setPadding(application.px2Px(binding.logo.paddingTop))
binding.main.layoutParams.width = application.px2Px(binding.main.layoutParams.width)
binding.main.setPadding(application.px2Px(binding.main.paddingTop))
val layoutParamsMain = binding.main.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsMain.leftMargin = application.px2Px(binding.main.marginLeft)
binding.main.layoutParams = layoutParamsMain
@@ -59,6 +60,7 @@ class InfoFragment : Fragment() {
val layoutParamsDesc = binding.desc.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsDesc.topMargin = application.px2Px(binding.desc.marginTop)
binding.desc.layoutParams = layoutParamsDesc
binding.title.textSize = application.px2PxFont(binding.title.textSize)
binding.desc.textSize = application.px2PxFont(binding.desc.textSize)
@@ -17,6 +17,7 @@ import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -27,6 +28,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private var ready = 0
private val playerFragment = PlayerFragment()
private val errorFragment = ErrorFragment()
private val mainFragment = MainFragment()
private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment()
@@ -53,7 +55,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -67,11 +68,13 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, errorFragment)
.add(R.id.main_browse_fragment, timeFragment)
.add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, channelFragment)
.add(R.id.main_browse_fragment, mainFragment)
.hide(mainFragment)
.hide(errorFragment)
.commit()
}
gestureDetector = GestureDetector(this, GestureListener())
@@ -103,6 +106,44 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
}
fun showErrorFragment(msg: String) {
errorFragment.show(msg)
if (errorFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.show(errorFragment)
.commit()
}
fun hideErrorFragment() {
errorFragment.show("")
if (!errorFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.hide(errorFragment)
.commit()
}
fun showPlayerFragment() {
if (playerFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.show(playerFragment)
.commit()
}
fun hidePlayerFragment() {
if (!playerFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.hide(playerFragment)
.commit()
}
private fun showChannel(channel: String) {
if (!mainFragment.isHidden) {
return
@@ -183,7 +224,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun fragmentReady(tag: String) {
ready++
Log.i(TAG, "ready $tag $ready ")
if (ready == 7) {
if (ready == 8) {
mainFragment.fragmentReady()
showTime()
}
@@ -20,8 +20,10 @@ import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.MenuBinding
import com.lizongying.mytv.databinding.RowBinding
import com.lizongying.mytv.models.ProgramType
import com.lizongying.mytv.models.TVList
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -183,8 +185,13 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
if (tvViewModel.errInfo.value != null
&& tvViewModel.getTV().id == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT)
.show()
if (tvViewModel.errInfo.value == "") {
(activity as? MainActivity)?.showPlayerFragment()
(activity as? MainActivity)?.hideErrorFragment()
} else {
(activity as? MainActivity)?.hidePlayerFragment()
(activity as? MainActivity)?.showErrorFragment(tvViewModel.errInfo.value.toString())
}
}
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
@@ -266,8 +273,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
}
override fun onItemHasFocus(tvViewModel: TVViewModel) {
tvListViewModel.setItemPositionCurrent(tvViewModel.getTV().id)
val row = tvViewModel.getRowPosition()
for (i in rowList) {
@@ -283,7 +288,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
}
override fun onItemClicked(tvViewModel: TVViewModel) {
Log.i(TAG, "onItemClicked")
if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment()
return
@@ -293,6 +297,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
itemPosition = tvViewModel.getTV().id
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("menu")
Log.i(TAG, "onItemClicked ${tvViewModel.getTV().id}")
}
(activity as? MainActivity)?.switchMainFragment()
}
@@ -410,8 +415,9 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
Log.i(TAG, "toPosition $rowPosition $itemPosition")
for (i in rowList) {
if (i.tag as Int == rowPosition) {
((i as RecyclerView).adapter as CardAdapter).focusable = true
((i as RecyclerView).adapter as CardAdapter).toPosition(itemPosition!!)
((i as RecyclerView).adapter as CardAdapter).updateEPG()
(i.adapter as CardAdapter).focusable = true
(i.adapter as CardAdapter).toPosition(itemPosition!!)
break
}
}
@@ -78,10 +78,18 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
Log.e(TAG, "PlaybackException $error")
val err = "播放错误"
tvViewModel?.setErrInfo(err)
tvViewModel?.changed("retry")
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (isPlaying) {
tvViewModel?.setErrInfo("")
}
}
})
}
})
@@ -18,7 +18,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.PermissionChecker
import androidx.core.content.PermissionChecker.checkSelfPermission
import com.lizongying.mytv.api.ReleaseV2
import com.lizongying.mytv.requests.MyRequest
import com.lizongying.mytv.requests.ReleaseRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -32,7 +32,7 @@ class UpdateManager(
) :
ConfirmationFragment.ConfirmationListener {
private var myRequest = MyRequest()
private var myRequest = ReleaseRequest()
private var release: ReleaseV2? = null
private var downloadReceiver: DownloadReceiver? = null
@@ -5,6 +5,7 @@ import android.os.Build
import android.util.TypedValue
import com.google.gson.Gson
import com.lizongying.mytv.api.TimeResponse
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
@@ -1,4 +1,4 @@
package com.lizongying.mytv
package com.lizongying.mytv.api
import android.content.Context
@@ -1,7 +1,6 @@
package com.lizongying.mytv.api
import android.content.Context
import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.SP
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel
@@ -1,4 +1,4 @@
package com.lizongying.mytv
package com.lizongying.mytv.models
import com.lizongying.mytv.models.ProgramType
import java.io.Serializable
@@ -28,6 +28,8 @@ data class TV(
", pid='" + pid + '\'' +
", sid='" + sid + '\'' +
", programType='" + programType + '\'' +
", needToken='" + needToken + '\'' +
", mustToken='" + mustToken + '\'' +
'}'
}
}
@@ -1,6 +1,6 @@
package com.lizongying.mytv
package com.lizongying.mytv.models
import com.lizongying.mytv.models.ProgramType
import com.lizongying.mytv.R
object TVList {
val list: Map<String, List<TV>> by lazy {
@@ -900,6 +900,7 @@ object TVList {
v.forEach { v1 ->
if (!v1.mustToken) {
v1.id = id
v1.needToken = false
id++
group.add(v1)
}
@@ -16,10 +16,6 @@ class TVListViewModel : ViewModel() {
val itemPosition: LiveData<Int>
get() = _itemPosition
private val _itemPositionCurrent = MutableLiveData<Int>()
val itemPositionCurrent: LiveData<Int>
get() = _itemPositionCurrent
fun addTVViewModel(tvViewModel: TVViewModel) {
if (_tvListViewModel.value == null) {
_tvListViewModel.value = mutableListOf(tvViewModel)
@@ -32,23 +28,15 @@ class TVListViewModel : ViewModel() {
return _tvListViewModel.value?.get(id)
}
fun getTVViewModelCurrent(): TVViewModel? {
return _itemPositionCurrent.value?.let { _tvListViewModel.value?.get(it) }
}
fun setItemPosition(position: Int) {
_itemPosition.value = position
_itemPositionCurrent.value = position
}
fun setItemPositionCurrent(position: Int) {
_itemPositionCurrent.value = position
}
fun size(): Int {
if (_tvListViewModel.value == null) {
return 0
}
return _tvListViewModel.value!!.size
}
}
@@ -1,10 +1,8 @@
package com.lizongying.mytv.models
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.lizongying.mytv.TV
import com.lizongying.mytv.api.FEPG
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
import com.tencent.videolite.android.datamodel.cctvjce.TVProgram
@@ -56,24 +54,15 @@ class TVViewModel(private var tv: TV) : ViewModel() {
if (_videoUrl.value!!.last().contains("cctv.cn")) {
tv.videoUrl = tv.videoUrl.subList(0, tv.videoUrl.lastIndex) + listOf(url)
} else {
tv.videoUrl = tv.videoUrl + listOf(url)
tv.videoUrl += listOf(url)
}
} else {
tv.videoUrl = tv.videoUrl + listOf(url)
tv.videoUrl += listOf(url)
}
_videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex
}
fun firstSource() {
if (_videoUrl.value!!.isNotEmpty()) {
setVideoIndex(0)
allReady()
} else {
Log.e(TAG, "no first")
}
}
fun changed(from: String) {
_change.value = from
}
@@ -82,10 +71,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
_ready.value = true
}
fun setVideoIndex(videoIndex: Int) {
_videoIndex.value = videoIndex
}
init {
_videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex
@@ -10,7 +10,7 @@ import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class MyRequest {
class ReleaseRequest {
private var releaseService = ApiClient().releaseService
suspend fun getRelease(): ReleaseV2? {
@@ -39,6 +39,6 @@ class MyRequest {
}
companion object {
private const val TAG = "MyRequest"
private const val TAG = "ReleaseRequest"
}
}
@@ -1,11 +1,10 @@
package com.lizongying.mytv
package com.lizongying.mytv.requests
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import com.google.gson.Gson
import java.net.URLDecoder
import com.lizongying.mytv.Utils
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.Auth
@@ -25,6 +24,7 @@ import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPJceService
import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.api.YSPTokenService
import com.lizongying.mytv.models.TVList
import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
@@ -118,11 +118,17 @@ object Request {
} else {
if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchAuth(tvModel, cookie)
}
} else {
val err = "认证结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
@@ -137,11 +143,17 @@ object Request {
} else {
if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchAuth(tvModel, cookie)
}
} else {
val err = "认证状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
@@ -158,11 +170,17 @@ object Request {
} else {
if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchAuth(tvModel, cookie)
}
} else {
val err = "认证请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
})
@@ -171,7 +189,7 @@ object Request {
private fun fetchVideo(tvModel: TVViewModel, cookie: String) {
cancelCall()
if (::btraceRunnable.isInitialized) {
if (Request::btraceRunnable.isInitialized) {
handler.removeCallbacks(btraceRunnable)
}
@@ -229,18 +247,25 @@ object Request {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
}
} else {
val err = "结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") {
Log.e(TAG, "$title error ${liveInfo.data.errinfo}")
tvModel.setErrInfo(liveInfo.data.errinfo)
val err = "版权错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} else {
Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
@@ -255,12 +280,18 @@ object Request {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
}
} else {
val err = "其他错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
@@ -278,12 +309,18 @@ object Request {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchVideo(tvModel, cookie)
// fetchAuth(tvModel, cookie)
}
} else {
val err = "状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
@@ -300,11 +337,17 @@ object Request {
} else {
if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie)
} else {
// TODO
}
}
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
})
@@ -340,6 +383,8 @@ object Request {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
}
@@ -355,6 +400,8 @@ object Request {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchAuth(tvModel, cookie)
} else {
// TODO
}
}
}
@@ -397,6 +444,8 @@ object Request {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchVideo(tvModel, cookie)
} else {
// TODO
}
}
}
@@ -412,6 +461,8 @@ object Request {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchVideo(tvModel, cookie)
} else {
// TODO
}
}
}
@@ -447,6 +498,10 @@ object Request {
if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) {
tvModel.tokenFHRetryTimes++
fetchFAuth(tvModel)
} else {
val err = "状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
@@ -456,6 +511,10 @@ object Request {
if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) {
tvModel.tokenFHRetryTimes++
fetchFAuth(tvModel)
} else {
val err = "请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
})
@@ -817,6 +876,6 @@ object Request {
}
fun setRequestListener(listener: RequestListener) {
this.listener = listener
Request.listener = listener
}
}